mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 07:17:17 +00:00
feat: pagination support for logs request
This commit is contained in:
parent
5e42dd4e70
commit
99eecdebf3
@ -174,9 +174,7 @@ Again, the most checks can be performed by `make check` command, though some add
|
|||||||
|
|
||||||
from marshmallow import Schema, fields
|
from marshmallow import Schema, fields
|
||||||
|
|
||||||
from ahriman.web.schemas.auth_schema import AuthSchema
|
from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNameSchema, PaginationSchema
|
||||||
from ahriman.web.schemas.error_schema import ErrorSchema
|
|
||||||
from ahriman.web.schemas.package_name_schema import PackageNameSchema
|
|
||||||
from ahriman.web.views.base import BaseView
|
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.cookies_schema(AuthSchema) # should be always presented
|
||||||
@aiohttp_apispec.match_info_schema(PackageNameSchema)
|
@aiohttp_apispec.match_info_schema(PackageNameSchema)
|
||||||
|
@aiohttp_apispec.querystring_schema(PaginationSchema)
|
||||||
@aiohttp_apispec.json_schema(RequestSchema(many=True))
|
@aiohttp_apispec.json_schema(RequestSchema(many=True))
|
||||||
async def post(self) -> None: ...
|
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
|
### 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.
|
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:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
: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
|
ahriman.web.schemas.pgp\_key\_id\_schema module
|
||||||
-----------------------------------------------
|
-----------------------------------------------
|
||||||
|
|
||||||
|
@ -8,9 +8,8 @@ Subpackages
|
|||||||
:maxdepth: 4
|
:maxdepth: 4
|
||||||
|
|
||||||
ahriman.web.views.api
|
ahriman.web.views.api
|
||||||
ahriman.web.views.service
|
ahriman.web.views.v1
|
||||||
ahriman.web.views.status
|
ahriman.web.views.v2
|
||||||
ahriman.web.views.user
|
|
||||||
|
|
||||||
Submodules
|
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({
|
$.ajax({
|
||||||
url: `/api/v1/packages/${packageBase}/logs`,
|
url: `/api/v2/packages/${packageBase}/logs`,
|
||||||
type: "GET",
|
type: "GET",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: response => {
|
success: response => {
|
||||||
packageInfo.text(`${response.package_base} ${response.status.status} at ${new Date(1000 * response.status.timestamp).toISOString()}`);
|
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.removeClass();
|
||||||
packageInfoModalHeader.addClass("modal-header");
|
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.architecture }}</td>
|
||||||
<td>{{ package.description }}</td>
|
<td>{{ package.description }}</td>
|
||||||
<td><a href="{{ package.url }}" title="{{ package.name }} upstream url">{{ package.url }}</a></td>
|
<td><a href="{{ package.url }}" title="{{ package.name }} upstream url">{{ package.url }}</a></td>
|
||||||
<td>{{ package.licenses|join("<br>"|safe) }}</td>
|
<td>{{ package.licenses | join("<br>" | safe) }}</td>
|
||||||
<td>{{ package.groups|join("<br>"|safe) }}</td>
|
<td>{{ package.groups | join("<br>" | safe) }}</td>
|
||||||
<td>{{ package.depends|join("<br>"|safe) }}</td>
|
<td>{{ package.depends | join("<br>" | safe) }}</td>
|
||||||
<td>{{ package.archive_size }}</td>
|
<td>{{ package.archive_size }}</td>
|
||||||
<td>{{ package.installed_size }}</td>
|
<td>{{ package.installed_size }}</td>
|
||||||
<td>{{ package.build_date }}</td>
|
<td>{{ package.build_date }}</td>
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
from sqlite3 import Connection
|
from sqlite3 import Connection
|
||||||
|
|
||||||
from ahriman.core.database.operations import Operations
|
from ahriman.core.database.operations import Operations
|
||||||
from ahriman.core.util import pretty_datetime
|
|
||||||
from ahriman.models.log_record_id import LogRecordId
|
from ahriman.models.log_record_id import LogRecordId
|
||||||
|
|
||||||
|
|
||||||
@ -29,29 +28,34 @@ class LogsOperations(Operations):
|
|||||||
logs 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
|
extract logs for specified package base
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
package_base(str): package base to extract logs
|
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:
|
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 [
|
return [
|
||||||
f"""[{pretty_datetime(row["created"])}] {row["record"]}"""
|
(row["created"], row["record"])
|
||||||
for row in connection.execute(
|
for row in connection.execute(
|
||||||
"""
|
"""
|
||||||
select created, record from logs where package_base = :package_base
|
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 self.with_connection(run)
|
||||||
return "\n".join(records)
|
|
||||||
|
|
||||||
def logs_insert(self, log_record_id: LogRecordId, created: float, record: str) -> None:
|
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:
|
if package.base in self.known:
|
||||||
self.known[package.base] = (package, status)
|
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
|
extract logs for the package base
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
package_base(str): package base
|
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:
|
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:
|
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.docs import DocsView
|
||||||
from ahriman.web.views.api.swagger import SwaggerView
|
from ahriman.web.views.api.swagger import SwaggerView
|
||||||
from ahriman.web.views.index import IndexView
|
from ahriman.web.views.index import IndexView
|
||||||
from ahriman.web.views.service.add import AddView
|
from ahriman.web.views import v1, v2
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["setup_routes"]
|
__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_static("/static", static_path, follow_symlinks=True)
|
||||||
|
|
||||||
application.router.add_view("/api/v1/service/add", AddView)
|
application.router.add_view("/api/v1/service/add", v1.AddView)
|
||||||
application.router.add_view("/api/v1/service/pgp", PGPView)
|
application.router.add_view("/api/v1/service/pgp", v1.PGPView)
|
||||||
application.router.add_view("/api/v1/service/rebuild", RebuildView)
|
application.router.add_view("/api/v1/service/rebuild", v1.RebuildView)
|
||||||
application.router.add_view("/api/v1/service/process/{process_id}", ProcessView)
|
application.router.add_view("/api/v1/service/process/{process_id}", v1.ProcessView)
|
||||||
application.router.add_view("/api/v1/service/remove", RemoveView)
|
application.router.add_view("/api/v1/service/remove", v1.RemoveView)
|
||||||
application.router.add_view("/api/v1/service/request", RequestView)
|
application.router.add_view("/api/v1/service/request", v1.RequestView)
|
||||||
application.router.add_view("/api/v1/service/search", SearchView)
|
application.router.add_view("/api/v1/service/search", v1.SearchView)
|
||||||
application.router.add_view("/api/v1/service/update", UpdateView)
|
application.router.add_view("/api/v1/service/update", v1.UpdateView)
|
||||||
application.router.add_view("/api/v1/service/upload", UploadView)
|
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", v1.PackagesView)
|
||||||
application.router.add_view("/api/v1/packages/{package}", PackageView)
|
application.router.add_view("/api/v1/packages/{package}", v1.PackageView)
|
||||||
application.router.add_view("/api/v1/packages/{package}/logs", LogsView)
|
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/login", v1.LoginView)
|
||||||
application.router.add_view("/api/v1/logout", LogoutView)
|
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.internal_status_schema import InternalStatusSchema
|
||||||
from ahriman.web.schemas.log_schema import LogSchema
|
from ahriman.web.schemas.log_schema import LogSchema
|
||||||
from ahriman.web.schemas.login_schema import LoginSchema
|
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.oauth2_schema import OAuth2Schema
|
||||||
from ahriman.web.schemas.package_name_schema import PackageNameSchema
|
from ahriman.web.schemas.package_name_schema import PackageNameSchema
|
||||||
from ahriman.web.schemas.package_names_schema import PackageNamesSchema
|
from ahriman.web.schemas.package_names_schema import PackageNamesSchema
|
||||||
from ahriman.web.schemas.package_properties_schema import PackagePropertiesSchema
|
from ahriman.web.schemas.package_properties_schema import PackagePropertiesSchema
|
||||||
from ahriman.web.schemas.package_schema import PackageSchema
|
from ahriman.web.schemas.package_schema import PackageSchema
|
||||||
from ahriman.web.schemas.package_status_schema import PackageStatusSimplifiedSchema, PackageStatusSchema
|
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_id_schema import PGPKeyIdSchema
|
||||||
from ahriman.web.schemas.pgp_key_schema import PGPKeySchema
|
from ahriman.web.schemas.pgp_key_schema import PGPKeySchema
|
||||||
from ahriman.web.schemas.process_id_schema import ProcessIdSchema
|
from ahriman.web.schemas.process_id_schema import ProcessIdSchema
|
||||||
|
@ -37,3 +37,21 @@ class LogsSchema(Schema):
|
|||||||
logs = fields.String(required=True, metadata={
|
logs = fields.String(required=True, metadata={
|
||||||
"description": "Full package log from the last build",
|
"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 aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
|
||||||
|
|
||||||
from ahriman.core.exceptions import UnknownPackageError
|
from ahriman.core.exceptions import UnknownPackageError
|
||||||
|
from ahriman.core.util import pretty_datetime
|
||||||
from ahriman.models.log_record_id import LogRecordId
|
from ahriman.models.log_record_id import LogRecordId
|
||||||
from ahriman.models.user_access import UserAccess
|
from ahriman.models.user_access import UserAccess
|
||||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, LogSchema, LogsSchema, PackageNameSchema
|
from ahriman.web.schemas import AuthSchema, ErrorSchema, LogSchema, LogsSchema, PackageNameSchema
|
||||||
@ -103,7 +104,7 @@ class LogsView(BaseView):
|
|||||||
response = {
|
response = {
|
||||||
"package_base": package_base,
|
"package_base": package_base,
|
||||||
"status": status.view(),
|
"status": status.view(),
|
||||||
"logs": logs
|
"logs": "\n".join(f"[{pretty_datetime(created)}] {message}" for created, message in logs)
|
||||||
}
|
}
|
||||||
return json_response(response)
|
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_insert(LogRecordId(package_python_schedule.base, "1"), 42.0, "message 3")
|
||||||
|
|
||||||
database.logs_remove(package_ahriman.base, "1")
|
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_ahriman.base) == [(42.0, "message 1")]
|
||||||
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_remove_full(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None:
|
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)
|
database.logs_remove(package_ahriman.base, None)
|
||||||
assert not database.logs_get(package_ahriman.base)
|
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:
|
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"), 43.0, "message 2")
|
||||||
database.logs_insert(LogRecordId(package_ahriman.base, "1"), 42.0, "message 1")
|
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
|
must return package logs
|
||||||
"""
|
"""
|
||||||
logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_get")
|
logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_get")
|
||||||
watcher.logs_get(package_ahriman.base)
|
watcher.logs_get(package_ahriman.base, 1, 2)
|
||||||
logs_mock.assert_called_once_with(package_ahriman.base)
|
logs_mock.assert_called_once_with(package_ahriman.base, 1, 2)
|
||||||
|
|
||||||
|
|
||||||
def test_logs_remove(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
|
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 unittest.mock import AsyncMock
|
||||||
|
|
||||||
from ahriman.models.user_access import UserAccess
|
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:
|
async def test_get_permission() -> None:
|
@ -4,7 +4,7 @@ from aiohttp.test_utils import TestClient
|
|||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
from ahriman.models.user_access import UserAccess
|
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:
|
async def test_get_permission() -> None:
|
@ -4,7 +4,7 @@ from aiohttp.test_utils import TestClient
|
|||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
from ahriman.models.user_access import UserAccess
|
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:
|
async def test_get_permission() -> None:
|
@ -5,7 +5,7 @@ from pytest_mock import MockerFixture
|
|||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
from ahriman.models.user_access import UserAccess
|
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:
|
async def test_get_permission() -> None:
|
@ -4,7 +4,7 @@ from aiohttp.test_utils import TestClient
|
|||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
from ahriman.models.user_access import UserAccess
|
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:
|
async def test_get_permission() -> None:
|
@ -5,7 +5,7 @@ from pytest_mock import MockerFixture
|
|||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
from ahriman.models.user_access import UserAccess
|
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:
|
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.aur_package import AURPackage
|
||||||
from ahriman.models.user_access import UserAccess
|
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:
|
async def test_get_permission() -> None:
|
@ -5,7 +5,7 @@ from pytest_mock import MockerFixture
|
|||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
from ahriman.models.user_access import UserAccess
|
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:
|
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.repository_paths import RepositoryPaths
|
||||||
from ahriman.models.user_access import UserAccess
|
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:
|
async def test_get_permission() -> None:
|
||||||
@ -30,7 +30,7 @@ async def test_save_file(mocker: MockerFixture) -> None:
|
|||||||
part_mock.filename = "filename"
|
part_mock.filename = "filename"
|
||||||
part_mock.read_chunk = AsyncMock(side_effect=[b"content", None])
|
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()
|
file_mock = MagicMock()
|
||||||
tempfile_mock.return_value.__enter__.return_value = file_mock
|
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
|
must process file upload via http
|
||||||
"""
|
"""
|
||||||
local = Path("local")
|
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")))
|
side_effect=AsyncMock(return_value=("filename", local / ".filename")))
|
||||||
rename_mock = mocker.patch("pathlib.Path.rename")
|
rename_mock = mocker.patch("pathlib.Path.rename")
|
||||||
# no content validation here because it has invalid schema
|
# 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
|
must process file upload with signature via http
|
||||||
"""
|
"""
|
||||||
local = Path("local")
|
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=[
|
side_effect=AsyncMock(side_effect=[
|
||||||
("filename", local / ".filename"),
|
("filename", local / ".filename"),
|
||||||
("filename.sig", local / ".filename.sig"),
|
("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.build_status import BuildStatusEnum
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.user_access import UserAccess
|
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:
|
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.build_status import BuildStatus, BuildStatusEnum
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.user_access import UserAccess
|
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:
|
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.build_status import BuildStatusEnum
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.user_access import UserAccess
|
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:
|
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.internal_status import InternalStatus
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.user_access import UserAccess
|
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:
|
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 import User
|
||||||
from ahriman.models.user_access import UserAccess
|
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:
|
async def test_get_permission() -> None:
|
@ -5,7 +5,7 @@ from aiohttp.web import HTTPUnauthorized
|
|||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
from ahriman.models.user_access import UserAccess
|
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:
|
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())
|
Loading…
Reference in New Issue
Block a user