mirror of
				https://github.com/arcan1s/ahriman.git
				synced 2025-10-30 21:33:43 +00:00 
			
		
		
		
	feat: pagination support for logs request
This commit is contained in:
		| @ -174,9 +174,7 @@ Again, the most checks can be performed by `make check` command, though some add | ||||
|  | ||||
|     from marshmallow import Schema, fields   | ||||
|  | ||||
|     from ahriman.web.schemas.auth_schema import AuthSchema | ||||
|     from ahriman.web.schemas.error_schema import ErrorSchema | ||||
|     from ahriman.web.schemas.package_name_schema import PackageNameSchema | ||||
|     from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNameSchema, PaginationSchema | ||||
|     from ahriman.web.views.base import BaseView | ||||
|  | ||||
|  | ||||
| @ -210,10 +208,14 @@ Again, the most checks can be performed by `make check` command, though some add | ||||
|         ) | ||||
|         @aiohttp_apispec.cookies_schema(AuthSchema)  # should be always presented | ||||
|         @aiohttp_apispec.match_info_schema(PackageNameSchema) | ||||
|         @aiohttp_apispec.querystring_schema(PaginationSchema) | ||||
|         @aiohttp_apispec.json_schema(RequestSchema(many=True)) | ||||
|         async def post(self) -> None: ... | ||||
|     ``` | ||||
|  | ||||
| * It is allowed to change web API to add new fields or remove optional ones. However, in case of model changes, new API version must be introduced. | ||||
| * On the other hand, it is allowed to change method signatures, however, it is recommended to add new parameters as optional if possible. Deprecated API can be dropped during major release. | ||||
|  | ||||
| ### Other checks | ||||
|  | ||||
| The projects also uses typing checks (provided by `mypy`) and some linter checks provided by `pylint` and `bandit`. Those checks must be passed successfully for any open pull requests. | ||||
|  | ||||
| @ -124,6 +124,14 @@ ahriman.web.schemas.package\_status\_schema module | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.schemas.pagination\_schema module | ||||
| --------------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.schemas.pagination_schema | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.schemas.pgp\_key\_id\_schema module | ||||
| ----------------------------------------------- | ||||
|  | ||||
|  | ||||
| @ -8,9 +8,8 @@ Subpackages | ||||
|    :maxdepth: 4 | ||||
|  | ||||
|    ahriman.web.views.api | ||||
|    ahriman.web.views.service | ||||
|    ahriman.web.views.status | ||||
|    ahriman.web.views.user | ||||
|    ahriman.web.views.v1 | ||||
|    ahriman.web.views.v2 | ||||
|  | ||||
| Submodules | ||||
| ---------- | ||||
|  | ||||
| @ -1,85 +0,0 @@ | ||||
| ahriman.web.views.service package | ||||
| ================================= | ||||
|  | ||||
| Submodules | ||||
| ---------- | ||||
|  | ||||
| ahriman.web.views.service.add module | ||||
| ------------------------------------ | ||||
|  | ||||
| .. automodule:: ahriman.web.views.service.add | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.service.pgp module | ||||
| ------------------------------------ | ||||
|  | ||||
| .. automodule:: ahriman.web.views.service.pgp | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.service.process module | ||||
| ---------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.service.process | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.service.rebuild module | ||||
| ---------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.service.rebuild | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.service.remove module | ||||
| --------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.service.remove | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.service.request module | ||||
| ---------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.service.request | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.service.search module | ||||
| --------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.service.search | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.service.update module | ||||
| --------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.service.update | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.service.upload module | ||||
| --------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.service.upload | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| Module contents | ||||
| --------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.service | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
| @ -1,45 +0,0 @@ | ||||
| ahriman.web.views.status package | ||||
| ================================ | ||||
|  | ||||
| Submodules | ||||
| ---------- | ||||
|  | ||||
| ahriman.web.views.status.logs module | ||||
| ------------------------------------ | ||||
|  | ||||
| .. automodule:: ahriman.web.views.status.logs | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.status.package module | ||||
| --------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.status.package | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.status.packages module | ||||
| ---------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.status.packages | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.status.status module | ||||
| -------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.status.status | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| Module contents | ||||
| --------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.status | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
| @ -1,29 +0,0 @@ | ||||
| ahriman.web.views.user package | ||||
| ============================== | ||||
|  | ||||
| Submodules | ||||
| ---------- | ||||
|  | ||||
| ahriman.web.views.user.login module | ||||
| ----------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.user.login | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.user.logout module | ||||
| ------------------------------------ | ||||
|  | ||||
| .. automodule:: ahriman.web.views.user.logout | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| Module contents | ||||
| --------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.user | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
							
								
								
									
										20
									
								
								docs/ahriman.web.views.v1.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								docs/ahriman.web.views.v1.rst
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| ahriman.web.views.v1 package | ||||
| ============================ | ||||
|  | ||||
| Subpackages | ||||
| ----------- | ||||
|  | ||||
| .. toctree:: | ||||
|    :maxdepth: 4 | ||||
|  | ||||
|    ahriman.web.views.v1.service | ||||
|    ahriman.web.views.v1.status | ||||
|    ahriman.web.views.v1.user | ||||
|  | ||||
| Module contents | ||||
| --------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1 | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
							
								
								
									
										85
									
								
								docs/ahriman.web.views.v1.service.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								docs/ahriman.web.views.v1.service.rst
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | ||||
| ahriman.web.views.v1.service package | ||||
| ==================================== | ||||
|  | ||||
| Submodules | ||||
| ---------- | ||||
|  | ||||
| ahriman.web.views.v1.service.add module | ||||
| --------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1.service.add | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.v1.service.pgp module | ||||
| --------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1.service.pgp | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.v1.service.process module | ||||
| ------------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1.service.process | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.v1.service.rebuild module | ||||
| ------------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1.service.rebuild | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.v1.service.remove module | ||||
| ------------------------------------------ | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1.service.remove | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.v1.service.request module | ||||
| ------------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1.service.request | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.v1.service.search module | ||||
| ------------------------------------------ | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1.service.search | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.v1.service.update module | ||||
| ------------------------------------------ | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1.service.update | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.v1.service.upload module | ||||
| ------------------------------------------ | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1.service.upload | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| Module contents | ||||
| --------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1.service | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
							
								
								
									
										45
									
								
								docs/ahriman.web.views.v1.status.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								docs/ahriman.web.views.v1.status.rst
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | ||||
| ahriman.web.views.v1.status package | ||||
| =================================== | ||||
|  | ||||
| Submodules | ||||
| ---------- | ||||
|  | ||||
| ahriman.web.views.v1.status.logs module | ||||
| --------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1.status.logs | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.v1.status.package module | ||||
| ------------------------------------------ | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1.status.package | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.v1.status.packages module | ||||
| ------------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1.status.packages | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.v1.status.status module | ||||
| ----------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1.status.status | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| Module contents | ||||
| --------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1.status | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
							
								
								
									
										29
									
								
								docs/ahriman.web.views.v1.user.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								docs/ahriman.web.views.v1.user.rst
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| ahriman.web.views.v1.user package | ||||
| ================================= | ||||
|  | ||||
| Submodules | ||||
| ---------- | ||||
|  | ||||
| ahriman.web.views.v1.user.login module | ||||
| -------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1.user.login | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| ahriman.web.views.v1.user.logout module | ||||
| --------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1.user.logout | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| Module contents | ||||
| --------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v1.user | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
							
								
								
									
										18
									
								
								docs/ahriman.web.views.v2.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								docs/ahriman.web.views.v2.rst
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| ahriman.web.views.v2 package | ||||
| ============================ | ||||
|  | ||||
| Subpackages | ||||
| ----------- | ||||
|  | ||||
| .. toctree:: | ||||
|    :maxdepth: 4 | ||||
|  | ||||
|    ahriman.web.views.v2.status | ||||
|  | ||||
| Module contents | ||||
| --------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v2 | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
							
								
								
									
										21
									
								
								docs/ahriman.web.views.v2.status.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								docs/ahriman.web.views.v2.status.rst
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| ahriman.web.views.v2.status package | ||||
| =================================== | ||||
|  | ||||
| Submodules | ||||
| ---------- | ||||
|  | ||||
| ahriman.web.views.v2.status.logs module | ||||
| --------------------------------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v2.status.logs | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
|  | ||||
| Module contents | ||||
| --------------- | ||||
|  | ||||
| .. automodule:: ahriman.web.views.v2.status | ||||
|    :members: | ||||
|    :no-undoc-members: | ||||
|    :show-inheritance: | ||||
| @ -45,12 +45,16 @@ | ||||
|         }; | ||||
|  | ||||
|         $.ajax({ | ||||
|             url: `/api/v1/packages/${packageBase}/logs`, | ||||
|             url: `/api/v2/packages/${packageBase}/logs`, | ||||
|             type: "GET", | ||||
|             dataType: "json", | ||||
|             success: response => { | ||||
|                 packageInfo.text(`${response.package_base} ${response.status.status} at ${new Date(1000 * response.status.timestamp).toISOString()}`); | ||||
|                 packageInfoLogsInput.text(response.logs); | ||||
|                 const logs = response.logs.map(log_record => { | ||||
|                     const [timestamp, record] = log_record; | ||||
|                     return `[${new Date(1000 * timestamp).toISOString()}] ${record}`; | ||||
|                 }); | ||||
|                 packageInfoLogsInput.text(logs.join("\n")); | ||||
|  | ||||
|                 packageInfoModalHeader.removeClass(); | ||||
|                 packageInfoModalHeader.addClass("modal-header"); | ||||
|  | ||||
| @ -73,9 +73,9 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa | ||||
|                         <td>{{ package.architecture }}</td> | ||||
|                         <td>{{ package.description }}</td> | ||||
|                         <td><a href="{{ package.url }}" title="{{ package.name }} upstream url">{{ package.url }}</a></td> | ||||
|                         <td>{{ package.licenses|join("<br>"|safe) }}</td> | ||||
|                         <td>{{ package.groups|join("<br>"|safe) }}</td> | ||||
|                         <td>{{ package.depends|join("<br>"|safe) }}</td> | ||||
|                         <td>{{ package.licenses | join("<br>" | safe) }}</td> | ||||
|                         <td>{{ package.groups | join("<br>" | safe) }}</td> | ||||
|                         <td>{{ package.depends | join("<br>" | safe) }}</td> | ||||
|                         <td>{{ package.archive_size }}</td> | ||||
|                         <td>{{ package.installed_size }}</td> | ||||
|                         <td>{{ package.build_date }}</td> | ||||
|  | ||||
| @ -20,7 +20,6 @@ | ||||
| from sqlite3 import Connection | ||||
|  | ||||
| from ahriman.core.database.operations import Operations | ||||
| from ahriman.core.util import pretty_datetime | ||||
| from ahriman.models.log_record_id import LogRecordId | ||||
|  | ||||
|  | ||||
| @ -29,29 +28,34 @@ class LogsOperations(Operations): | ||||
|     logs operations | ||||
|     """ | ||||
|  | ||||
|     def logs_get(self, package_base: str) -> str: | ||||
|     def logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[tuple[float, str]]: | ||||
|         """ | ||||
|         extract logs for specified package base | ||||
|  | ||||
|         Args: | ||||
|             package_base(str): package base to extract logs | ||||
|             limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1) | ||||
|             offset(int, optional): records offset (Default value = 0) | ||||
|  | ||||
|         Return: | ||||
|             str: full package log | ||||
|             list[tuple[float, str]]: sorted package log records and their timestamps | ||||
|         """ | ||||
|         def run(connection: Connection) -> list[str]: | ||||
|         def run(connection: Connection) -> list[tuple[float, str]]: | ||||
|             return [ | ||||
|                 f"""[{pretty_datetime(row["created"])}] {row["record"]}""" | ||||
|                 (row["created"], row["record"]) | ||||
|                 for row in connection.execute( | ||||
|                     """ | ||||
|                     select created, record from logs where package_base = :package_base | ||||
|                     order by created | ||||
|                     order by created limit :limit offset :offset | ||||
|                     """, | ||||
|                     {"package_base": package_base}) | ||||
|                     { | ||||
|                         "package_base": package_base, | ||||
|                         "limit": limit, | ||||
|                         "offset": offset, | ||||
|                     }) | ||||
|             ] | ||||
|  | ||||
|         records = self.with_connection(run) | ||||
|         return "\n".join(records) | ||||
|         return self.with_connection(run) | ||||
|  | ||||
|     def logs_insert(self, log_record_id: LogRecordId, created: float, record: str) -> None: | ||||
|         """ | ||||
|  | ||||
| @ -85,17 +85,19 @@ class Watcher(LazyLogging): | ||||
|             if package.base in self.known: | ||||
|                 self.known[package.base] = (package, status) | ||||
|  | ||||
|     def logs_get(self, package_base: str) -> str: | ||||
|     def logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[tuple[float, str]]: | ||||
|         """ | ||||
|         extract logs for the package base | ||||
|  | ||||
|         Args: | ||||
|             package_base(str): package base | ||||
|             limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1) | ||||
|             offset(int, optional): records offset (Default value = 0) | ||||
|  | ||||
|         Returns: | ||||
|             str: package logs | ||||
|             list[tuple[float, str]]: package logs | ||||
|         """ | ||||
|         return self.database.logs_get(package_base) | ||||
|         return self.database.logs_get(package_base, limit, offset) | ||||
|  | ||||
|     def logs_remove(self, package_base: str, version: str | None) -> None: | ||||
|         """ | ||||
|  | ||||
| @ -23,21 +23,7 @@ from pathlib import Path | ||||
| 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.service.add import AddView | ||||
| from ahriman.web.views.service.pgp import PGPView | ||||
| from ahriman.web.views.service.process import ProcessView | ||||
| from ahriman.web.views.service.rebuild import RebuildView | ||||
| from ahriman.web.views.service.remove import RemoveView | ||||
| from ahriman.web.views.service.request import RequestView | ||||
| from ahriman.web.views.service.search import SearchView | ||||
| from ahriman.web.views.service.update import UpdateView | ||||
| from ahriman.web.views.service.upload import UploadView | ||||
| from ahriman.web.views.status.logs import LogsView | ||||
| from ahriman.web.views.status.package import PackageView | ||||
| from ahriman.web.views.status.packages import PackagesView | ||||
| from ahriman.web.views.status.status import StatusView | ||||
| from ahriman.web.views.user.login import LoginView | ||||
| from ahriman.web.views.user.logout import LogoutView | ||||
| from ahriman.web.views import v1, v2 | ||||
|  | ||||
|  | ||||
| __all__ = ["setup_routes"] | ||||
| @ -59,21 +45,22 @@ def setup_routes(application: Application, static_path: Path) -> None: | ||||
|  | ||||
|     application.router.add_static("/static", static_path, follow_symlinks=True) | ||||
|  | ||||
|     application.router.add_view("/api/v1/service/add", AddView) | ||||
|     application.router.add_view("/api/v1/service/pgp", PGPView) | ||||
|     application.router.add_view("/api/v1/service/rebuild", RebuildView) | ||||
|     application.router.add_view("/api/v1/service/process/{process_id}", ProcessView) | ||||
|     application.router.add_view("/api/v1/service/remove", RemoveView) | ||||
|     application.router.add_view("/api/v1/service/request", RequestView) | ||||
|     application.router.add_view("/api/v1/service/search", SearchView) | ||||
|     application.router.add_view("/api/v1/service/update", UpdateView) | ||||
|     application.router.add_view("/api/v1/service/upload", UploadView) | ||||
|     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", PackagesView) | ||||
|     application.router.add_view("/api/v1/packages/{package}", PackageView) | ||||
|     application.router.add_view("/api/v1/packages/{package}/logs", LogsView) | ||||
|     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", StatusView) | ||||
|     application.router.add_view("/api/v1/status", v1.StatusView) | ||||
|  | ||||
|     application.router.add_view("/api/v1/login", LoginView) | ||||
|     application.router.add_view("/api/v1/logout", LogoutView) | ||||
|     application.router.add_view("/api/v1/login", v1.LoginView) | ||||
|     application.router.add_view("/api/v1/logout", v1.LogoutView) | ||||
|  | ||||
| @ -25,13 +25,14 @@ from ahriman.web.schemas.file_schema import FileSchema | ||||
| from ahriman.web.schemas.internal_status_schema import InternalStatusSchema | ||||
| from ahriman.web.schemas.log_schema import LogSchema | ||||
| from ahriman.web.schemas.login_schema import LoginSchema | ||||
| from ahriman.web.schemas.logs_schema import LogsSchema | ||||
| from ahriman.web.schemas.logs_schema import LogsSchema, LogsSchemaV2 | ||||
| from ahriman.web.schemas.oauth2_schema import OAuth2Schema | ||||
| from ahriman.web.schemas.package_name_schema import PackageNameSchema | ||||
| from ahriman.web.schemas.package_names_schema import PackageNamesSchema | ||||
| from ahriman.web.schemas.package_properties_schema import PackagePropertiesSchema | ||||
| from ahriman.web.schemas.package_schema import PackageSchema | ||||
| from ahriman.web.schemas.package_status_schema import PackageStatusSimplifiedSchema, PackageStatusSchema | ||||
| from ahriman.web.schemas.pagination_schema import PaginationSchema | ||||
| from ahriman.web.schemas.pgp_key_id_schema import PGPKeyIdSchema | ||||
| from ahriman.web.schemas.pgp_key_schema import PGPKeySchema | ||||
| from ahriman.web.schemas.process_id_schema import ProcessIdSchema | ||||
|  | ||||
| @ -37,3 +37,21 @@ class LogsSchema(Schema): | ||||
|     logs = fields.String(required=True, metadata={ | ||||
|         "description": "Full package log from the last build", | ||||
|     }) | ||||
|  | ||||
|  | ||||
| class LogsSchemaV2(Schema): | ||||
|     """ | ||||
|     response package logs api v2 schema | ||||
|     """ | ||||
|  | ||||
|     package_base = fields.String(required=True, metadata={ | ||||
|         "description": "Package base name", | ||||
|         "example": "ahriman", | ||||
|     }) | ||||
|     status = fields.Nested(StatusSchema(), required=True, metadata={ | ||||
|         "description": "Last package status", | ||||
|     }) | ||||
|     logs = fields.List(fields.Tuple([fields.Float(), fields.String()]), required=True, metadata={  # type: ignore[no-untyped-call] | ||||
|         "description": "Package log records timestamp and message", | ||||
|         "example": [(1680537091.233495, "log record")] | ||||
|     }) | ||||
|  | ||||
							
								
								
									
										35
									
								
								src/ahriman/web/schemas/pagination_schema.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/ahriman/web/schemas/pagination_schema.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| # | ||||
| # 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 marshmallow import Schema, fields | ||||
|  | ||||
|  | ||||
| class PaginationSchema(Schema): | ||||
|     """ | ||||
|     request pagination schema | ||||
|     """ | ||||
|  | ||||
|     limit = fields.Integer(metadata={ | ||||
|         "description": "Limit records by specified amount", | ||||
|         "example": 42, | ||||
|     }) | ||||
|     offset = fields.Integer(metadata={ | ||||
|         "description": "Start records with the offset", | ||||
|         "example": 100, | ||||
|     }) | ||||
							
								
								
									
										36
									
								
								src/ahriman/web/views/v1/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/ahriman/web/views/v1/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | ||||
| # | ||||
| # 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.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 | ||||
| @ -22,6 +22,7 @@ import aiohttp_apispec  # type: ignore[import] | ||||
| from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response | ||||
| 
 | ||||
| from ahriman.core.exceptions import UnknownPackageError | ||||
| from ahriman.core.util import pretty_datetime | ||||
| from ahriman.models.log_record_id import LogRecordId | ||||
| from ahriman.models.user_access import UserAccess | ||||
| from ahriman.web.schemas import AuthSchema, ErrorSchema, LogSchema, LogsSchema, PackageNameSchema | ||||
| @ -103,7 +104,7 @@ class LogsView(BaseView): | ||||
|         response = { | ||||
|             "package_base": package_base, | ||||
|             "status": status.view(), | ||||
|             "logs": logs | ||||
|             "logs": "\n".join(f"[{pretty_datetime(created)}] {message}" for created, message in logs) | ||||
|         } | ||||
|         return json_response(response) | ||||
| 
 | ||||
							
								
								
									
										20
									
								
								src/ahriman/web/views/v2/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/ahriman/web/views/v2/__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.web.views.v2.status.logs import LogsView | ||||
							
								
								
									
										19
									
								
								src/ahriman/web/views/v2/status/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/ahriman/web/views/v2/status/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| # | ||||
| # 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/>. | ||||
| # | ||||
							
								
								
									
										87
									
								
								src/ahriman/web/views/v2/status/logs.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/ahriman/web/views/v2/status/logs.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,87 @@ | ||||
| # | ||||
| # 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 aiohttp_apispec  # type: ignore[import] | ||||
|  | ||||
| from aiohttp.web import HTTPBadRequest, HTTPNotFound, Response, json_response | ||||
|  | ||||
| from ahriman.core.exceptions import UnknownPackageError | ||||
| from ahriman.models.user_access import UserAccess | ||||
| from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNameSchema, PaginationSchema | ||||
| from ahriman.web.schemas.logs_schema import LogsSchemaV2 | ||||
| from ahriman.web.views.base import BaseView | ||||
|  | ||||
|  | ||||
| class LogsView(BaseView): | ||||
|     """ | ||||
|     package logs web view | ||||
|  | ||||
|     Attributes: | ||||
|         GET_PERMISSION(UserAccess): (class attribute) get permissions of self | ||||
|     """ | ||||
|  | ||||
|     GET_PERMISSION = UserAccess.Reporter | ||||
|  | ||||
|     @aiohttp_apispec.docs( | ||||
|         tags=["Packages"], | ||||
|         summary="Get paginated package logs", | ||||
|         description="Retrieve package logs and the last package status", | ||||
|         responses={ | ||||
|             200: {"description": "Success response", "schema": LogsSchemaV2}, | ||||
|             400: {"description": "Bad data is supplied", "schema": ErrorSchema}, | ||||
|             401: {"description": "Authorization required", "schema": ErrorSchema}, | ||||
|             403: {"description": "Access is forbidden", "schema": ErrorSchema}, | ||||
|             404: {"description": "Package base is unknown", "schema": ErrorSchema}, | ||||
|             500: {"description": "Internal server error", "schema": ErrorSchema}, | ||||
|         }, | ||||
|         security=[{"token": [GET_PERMISSION]}], | ||||
|     ) | ||||
|     @aiohttp_apispec.cookies_schema(AuthSchema) | ||||
|     @aiohttp_apispec.match_info_schema(PackageNameSchema) | ||||
|     @aiohttp_apispec.querystring_schema(PaginationSchema) | ||||
|     async def get(self) -> Response: | ||||
|         """ | ||||
|         get last package logs | ||||
|  | ||||
|         Returns: | ||||
|             Response: 200 with package logs on success | ||||
|  | ||||
|         Raises: | ||||
|             HTTPBadRequest: if supplied parameters are invalid | ||||
|             HTTPNotFound: if package base is unknown | ||||
|         """ | ||||
|         package_base = self.request.match_info["package"] | ||||
|         try: | ||||
|             limit = int(self.request.query.getone("limit", default=-1)) | ||||
|             offset = int(self.request.query.getone("offset", default=0)) | ||||
|         except Exception as ex: | ||||
|             raise HTTPBadRequest(reason=str(ex)) | ||||
|  | ||||
|         try: | ||||
|             _, status = self.service.package_get(package_base) | ||||
|         except UnknownPackageError: | ||||
|             raise HTTPNotFound | ||||
|         logs = self.service.logs_get(package_base, limit, offset) | ||||
|  | ||||
|         response = { | ||||
|             "package_base": package_base, | ||||
|             "status": status.view(), | ||||
|             "logs": logs, | ||||
|         } | ||||
|         return json_response(response) | ||||
| @ -13,8 +13,8 @@ def test_logs_insert_remove_process(database: SQLite, package_ahriman: Package, | ||||
|     database.logs_insert(LogRecordId(package_python_schedule.base, "1"), 42.0, "message 3") | ||||
|  | ||||
|     database.logs_remove(package_ahriman.base, "1") | ||||
|     assert database.logs_get(package_ahriman.base) == "[1970-01-01 00:00:42] message 1" | ||||
|     assert database.logs_get(package_python_schedule.base) == "[1970-01-01 00:00:42] message 3" | ||||
|     assert database.logs_get(package_ahriman.base) == [(42.0, "message 1")] | ||||
|     assert database.logs_get(package_python_schedule.base) == [(42.0, "message 3")] | ||||
|  | ||||
|  | ||||
| def test_logs_insert_remove_full(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None: | ||||
| @ -27,7 +27,7 @@ def test_logs_insert_remove_full(database: SQLite, package_ahriman: Package, pac | ||||
|  | ||||
|     database.logs_remove(package_ahriman.base, None) | ||||
|     assert not database.logs_get(package_ahriman.base) | ||||
|     assert database.logs_get(package_python_schedule.base) == "[1970-01-01 00:00:42] message 3" | ||||
|     assert database.logs_get(package_python_schedule.base) == [(42.0, "message 3")] | ||||
|  | ||||
|  | ||||
| def test_logs_insert_get(database: SQLite, package_ahriman: Package) -> None: | ||||
| @ -36,4 +36,13 @@ def test_logs_insert_get(database: SQLite, package_ahriman: Package) -> None: | ||||
|     """ | ||||
|     database.logs_insert(LogRecordId(package_ahriman.base, "1"), 43.0, "message 2") | ||||
|     database.logs_insert(LogRecordId(package_ahriman.base, "1"), 42.0, "message 1") | ||||
|     assert database.logs_get(package_ahriman.base) == "[1970-01-01 00:00:42] message 1\n[1970-01-01 00:00:43] message 2" | ||||
|     assert database.logs_get(package_ahriman.base) == [(42.0, "message 1"), (43.0, "message 2")] | ||||
|  | ||||
|  | ||||
| def test_logs_insert_get_pagination(database: SQLite, package_ahriman: Package) -> None: | ||||
|     """ | ||||
|     must insert and get package logs with pagination | ||||
|     """ | ||||
|     database.logs_insert(LogRecordId(package_ahriman.base, "1"), 42.0, "message 1") | ||||
|     database.logs_insert(LogRecordId(package_ahriman.base, "1"), 43.0, "message 2") | ||||
|     assert database.logs_get(package_ahriman.base, 1, 1) == [(43.0, "message 2")] | ||||
|  | ||||
| @ -55,8 +55,8 @@ def test_logs_get(watcher: Watcher, package_ahriman: Package, mocker: MockerFixt | ||||
|     must return package logs | ||||
|     """ | ||||
|     logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_get") | ||||
|     watcher.logs_get(package_ahriman.base) | ||||
|     logs_mock.assert_called_once_with(package_ahriman.base) | ||||
|     watcher.logs_get(package_ahriman.base, 1, 2) | ||||
|     logs_mock.assert_called_once_with(package_ahriman.base, 1, 2) | ||||
|  | ||||
|  | ||||
| def test_logs_remove(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: | ||||
|  | ||||
							
								
								
									
										27
									
								
								tests/ahriman/test_tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								tests/ahriman/test_tests.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | ||||
| from pathlib import Path | ||||
|  | ||||
| from ahriman.core.util import walk | ||||
|  | ||||
|  | ||||
| def test_test_coverage() -> None: | ||||
|     """ | ||||
|     must have test files for each source file | ||||
|     """ | ||||
|     root = Path() | ||||
|     for source_file in filter(lambda fn: fn.suffix == ".py" and fn.name != "__init__.py", walk(root / "src")): | ||||
|         # some workaround for well known files | ||||
|         if source_file.parts[2:4] == ("application", "handlers") and source_file.name != "handler.py": | ||||
|             filename = f"test_handler_{source_file.name}" | ||||
|         elif source_file.parts[2:4] == ("web", "views"): | ||||
|             if (api := source_file.parts[4]) == "api": | ||||
|                 filename = f"test_view_{api}_{source_file.name}" | ||||
|             elif (version := source_file.parts[4]) in ("v1", "v2"): | ||||
|                 api = source_file.parts[5] | ||||
|                 filename = f"test_view_{version}_{api}_{source_file.name}" | ||||
|             else: | ||||
|                 filename = f"test_view_{source_file.name}" | ||||
|         else: | ||||
|             filename = f"test_{source_file.name}" | ||||
|  | ||||
|         test_file = Path("tests", *source_file.parts[1:-1], filename) | ||||
|         assert test_file.is_file(), test_file | ||||
							
								
								
									
										1
									
								
								tests/ahriman/web/schemas/test_pagination_schema.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/ahriman/web/schemas/test_pagination_schema.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| # schema testing goes in view class tests | ||||
| @ -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.service.add import AddView | ||||
| from ahriman.web.views.v1 import AddView | ||||
| 
 | ||||
| 
 | ||||
| async def test_get_permission() -> None: | ||||
| @ -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.service.pgp import PGPView | ||||
| from ahriman.web.views.v1 import PGPView | ||||
| 
 | ||||
| 
 | ||||
| async def test_get_permission() -> None: | ||||
| @ -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.service.process import ProcessView | ||||
| from ahriman.web.views.v1 import ProcessView | ||||
| 
 | ||||
| 
 | ||||
| async def test_get_permission() -> None: | ||||
| @ -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.service.rebuild import RebuildView | ||||
| from ahriman.web.views.v1 import RebuildView | ||||
| 
 | ||||
| 
 | ||||
| async def test_get_permission() -> None: | ||||
| @ -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.service.remove import RemoveView | ||||
| from ahriman.web.views.v1 import RemoveView | ||||
| 
 | ||||
| 
 | ||||
| async def test_get_permission() -> None: | ||||
| @ -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.service.request import RequestView | ||||
| from ahriman.web.views.v1 import RequestView | ||||
| 
 | ||||
| 
 | ||||
| async def test_get_permission() -> None: | ||||
| @ -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.service.search import SearchView | ||||
| from ahriman.web.views.v1 import SearchView | ||||
| 
 | ||||
| 
 | ||||
| async def test_get_permission() -> None: | ||||
| @ -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.service.update import UpdateView | ||||
| from ahriman.web.views.v1 import UpdateView | ||||
| 
 | ||||
| 
 | ||||
| async def test_get_permission() -> None: | ||||
| @ -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.service.upload import UploadView | ||||
| from ahriman.web.views.v1 import UploadView | ||||
| 
 | ||||
| 
 | ||||
| async def test_get_permission() -> None: | ||||
| @ -30,7 +30,7 @@ async def test_save_file(mocker: MockerFixture) -> None: | ||||
|     part_mock.filename = "filename" | ||||
|     part_mock.read_chunk = AsyncMock(side_effect=[b"content", None]) | ||||
| 
 | ||||
|     tempfile_mock = mocker.patch("ahriman.web.views.service.upload.NamedTemporaryFile") | ||||
|     tempfile_mock = mocker.patch("ahriman.web.views.v1.service.upload.NamedTemporaryFile") | ||||
|     file_mock = MagicMock() | ||||
|     tempfile_mock.return_value.__enter__.return_value = file_mock | ||||
| 
 | ||||
| @ -84,7 +84,7 @@ 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.service.upload.UploadView.save_file", | ||||
|     save_mock = mocker.patch("ahriman.web.views.v1.UploadView.save_file", | ||||
|                              side_effect=AsyncMock(return_value=("filename", local / ".filename"))) | ||||
|     rename_mock = mocker.patch("pathlib.Path.rename") | ||||
|     # no content validation here because it has invalid schema | ||||
| @ -103,7 +103,7 @@ 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.service.upload.UploadView.save_file", | ||||
|     save_mock = mocker.patch("ahriman.web.views.v1.UploadView.save_file", | ||||
|                              side_effect=AsyncMock(side_effect=[ | ||||
|                                  ("filename", local / ".filename"), | ||||
|                                  ("filename.sig", local / ".filename.sig"), | ||||
| @ -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.status.logs import LogsView | ||||
| from ahriman.web.views.v1 import LogsView | ||||
| 
 | ||||
| 
 | ||||
| async def test_get_permission() -> None: | ||||
| @ -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.status.package import PackageView | ||||
| from ahriman.web.views.v1 import PackageView | ||||
| 
 | ||||
| 
 | ||||
| async def test_get_permission() -> None: | ||||
| @ -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.status.packages import PackagesView | ||||
| from ahriman.web.views.v1 import PackagesView | ||||
| 
 | ||||
| 
 | ||||
| async def test_get_permission() -> None: | ||||
| @ -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.status.status import StatusView | ||||
| from ahriman.web.views.v1 import StatusView | ||||
| 
 | ||||
| 
 | ||||
| async def test_get_permission() -> None: | ||||
| @ -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.user.login import LoginView | ||||
| from ahriman.web.views.v1 import LoginView | ||||
| 
 | ||||
| 
 | ||||
| async def test_get_permission() -> None: | ||||
| @ -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.user.logout import LogoutView | ||||
| from ahriman.web.views.v1 import LogoutView | ||||
| 
 | ||||
| 
 | ||||
| async def test_get_permission() -> None: | ||||
| @ -0,0 +1,93 @@ | ||||
| import pytest | ||||
|  | ||||
| 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 | ||||
|  | ||||
|  | ||||
| async def test_get_permission() -> None: | ||||
|     """ | ||||
|     must return correct permission for the request | ||||
|     """ | ||||
|     for method in ("GET",): | ||||
|         request = pytest.helpers.request("", "", method) | ||||
|         assert await LogsView.get_permission(request) == UserAccess.Reporter | ||||
|  | ||||
|  | ||||
| async def test_get(client: TestClient, package_ahriman: Package) -> None: | ||||
|     """ | ||||
|     must get logs for package | ||||
|     """ | ||||
|     await client.post(f"/api/v1/packages/{package_ahriman.base}", | ||||
|                       json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()}) | ||||
|     await client.post(f"/api/v1/packages/{package_ahriman.base}/logs", | ||||
|                       json={"created": 42.0, "message": "message 1", "version": "42"}) | ||||
|     await client.post(f"/api/v1/packages/{package_ahriman.base}/logs", | ||||
|                       json={"created": 43.0, "message": "message 2", "version": "42"}) | ||||
|     request_schema = pytest.helpers.schema_request(LogsView.get, location="querystring") | ||||
|     response_schema = pytest.helpers.schema_response(LogsView.get) | ||||
|  | ||||
|     payload = {} | ||||
|     assert not request_schema.validate(payload) | ||||
|     response = await client.get(f"/api/v2/packages/{package_ahriman.base}/logs", params=payload) | ||||
|     assert response.status == 200 | ||||
|  | ||||
|     logs = await response.json() | ||||
|     assert not response_schema.validate(logs) | ||||
|     assert logs["logs"] == [[42.0, "message 1"], [43.0, "message 2"]] | ||||
|  | ||||
|  | ||||
| async def test_get_with_pagination(client: TestClient, package_ahriman: Package) -> None: | ||||
|     """ | ||||
|     must get logs with pagination | ||||
|     """ | ||||
|     await client.post(f"/api/v1/packages/{package_ahriman.base}", | ||||
|                       json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()}) | ||||
|     await client.post(f"/api/v1/packages/{package_ahriman.base}/logs", | ||||
|                       json={"created": 42.0, "message": "message 1", "version": "42"}) | ||||
|     await client.post(f"/api/v1/packages/{package_ahriman.base}/logs", | ||||
|                       json={"created": 43.0, "message": "message 2", "version": "42"}) | ||||
|     request_schema = pytest.helpers.schema_request(LogsView.get, location="querystring") | ||||
|     response_schema = pytest.helpers.schema_response(LogsView.get) | ||||
|  | ||||
|     payload = {"limit": 1, "offset": 1} | ||||
|     assert not request_schema.validate(payload) | ||||
|     response = await client.get(f"/api/v2/packages/{package_ahriman.base}/logs", params=payload) | ||||
|     assert response.status == 200 | ||||
|  | ||||
|     logs = await response.json() | ||||
|     assert not response_schema.validate(logs) | ||||
|     assert logs["logs"] == [[43.0, "message 2"]] | ||||
|  | ||||
|  | ||||
| async def test_get_not_found(client: TestClient, package_ahriman: Package) -> None: | ||||
|     """ | ||||
|     must return not found for missing package | ||||
|     """ | ||||
|     response_schema = pytest.helpers.schema_response(LogsView.get, code=404) | ||||
|  | ||||
|     response = await client.get(f"/api/v2/packages/{package_ahriman.base}/logs") | ||||
|     assert response.status == 404 | ||||
|     assert not response_schema.validate(await response.json()) | ||||
|  | ||||
|  | ||||
| async def test_get_bad_request(client: TestClient, package_ahriman: Package) -> None: | ||||
|     """ | ||||
|     must return bad request for invalid query parameters | ||||
|     """ | ||||
|     await client.post(f"/api/v1/packages/{package_ahriman.base}", | ||||
|                       json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()}) | ||||
|     await client.post(f"/api/v1/packages/{package_ahriman.base}/logs", | ||||
|                       json={"created": 42.0, "message": "message", "version": "42"}) | ||||
|     response_schema = pytest.helpers.schema_response(LogsView.get, code=400) | ||||
|  | ||||
|     response = await client.get(f"/api/v2/packages/{package_ahriman.base}/logs", params={"limit": "limit"}) | ||||
|     assert response.status == 400 | ||||
|     assert not response_schema.validate(await response.json()) | ||||
|  | ||||
|     response = await client.get(f"/api/v2/packages/{package_ahriman.base}/logs", params={"offset": "offset"}) | ||||
|     assert response.status == 400 | ||||
|     assert not response_schema.validate(await response.json()) | ||||
		Reference in New Issue
	
	Block a user