mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-06-28 14:51:43 +00:00
feat: implement audit log tables and methods (#129)
This commit is contained in:
@ -0,0 +1,8 @@
|
||||
from ahriman.core.database.migrations.m014_auditlog import steps
|
||||
|
||||
|
||||
def test_migration_auditlog() -> None:
|
||||
"""
|
||||
migration must not be empty
|
||||
"""
|
||||
assert steps
|
@ -0,0 +1,40 @@
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.models.event import Event, EventType
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
def test_event_insert_get(database: SQLite, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must insert and get event
|
||||
"""
|
||||
event = Event(EventType.PackageUpdated, package_ahriman.base, "Updated", {"key": "value"})
|
||||
database.event_insert(event)
|
||||
assert database.event_get() == [event]
|
||||
|
||||
event2 = Event("event", "object")
|
||||
database.event_insert(event2, RepositoryId("i686", database._repository_id.name))
|
||||
assert database.event_get() == [event]
|
||||
assert database.event_get(repository_id=RepositoryId("i686", database._repository_id.name)) == [event2]
|
||||
|
||||
|
||||
def test_event_insert_get_filter(database: SQLite) -> None:
|
||||
"""
|
||||
must insert and get events with filter
|
||||
"""
|
||||
database.event_insert(Event("event 1", "object 1", created=1))
|
||||
database.event_insert(Event("event 2", "object 2"))
|
||||
database.event_insert(Event(EventType.PackageUpdated, "package"))
|
||||
|
||||
assert database.event_get(event="event 1") == [Event("event 1", "object 1", created=1)]
|
||||
assert database.event_get(object_id="object 1") == [Event("event 1", "object 1", created=1)]
|
||||
assert all(event.event == EventType.PackageUpdated for event in database.event_get(event=EventType.PackageUpdated))
|
||||
|
||||
|
||||
def test_event_insert_get_pagination(database: SQLite) -> None:
|
||||
"""
|
||||
must insert and get events with pagination
|
||||
"""
|
||||
database.event_insert(Event("1", "1"))
|
||||
database.event_insert(Event("2", "2"))
|
||||
assert all(event.event == "2" for event in database.event_get(limit=1, offset=1))
|
@ -11,6 +11,7 @@ from ahriman.core.status.web_client import WebClient
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.changes import Changes
|
||||
from ahriman.models.dependencies import Dependencies
|
||||
from ahriman.models.event import Event
|
||||
from ahriman.models.internal_status import InternalStatus
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.package import Package
|
||||
@ -94,6 +95,22 @@ def test_load_web_client_from_legacy_unix_socket(configuration: Configuration, d
|
||||
assert isinstance(Client.load(repository_id, configuration, database, report=True), WebClient)
|
||||
|
||||
|
||||
def test_event_add(client: Client) -> None:
|
||||
"""
|
||||
must raise not implemented on event insertion
|
||||
"""
|
||||
with pytest.raises(NotImplementedError):
|
||||
client.event_add(Event("", ""))
|
||||
|
||||
|
||||
def test_event_get(client: Client) -> None:
|
||||
"""
|
||||
must raise not implemented on events request
|
||||
"""
|
||||
with pytest.raises(NotImplementedError):
|
||||
client.event_get(None, None)
|
||||
|
||||
|
||||
def test_package_changes_get(client: Client, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must raise not implemented on package changes request
|
||||
|
@ -7,11 +7,32 @@ from ahriman.core.status.local_client import LocalClient
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.changes import Changes
|
||||
from ahriman.models.dependencies import Dependencies
|
||||
from ahriman.models.event import Event, EventType
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
|
||||
|
||||
def test_event_add(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add new event
|
||||
"""
|
||||
event_mock = mocker.patch("ahriman.core.database.SQLite.event_insert")
|
||||
event = Event(EventType.PackageUpdated, package_ahriman.base)
|
||||
|
||||
local_client.event_add(event)
|
||||
event_mock.assert_called_once_with(event, local_client.repository_id)
|
||||
|
||||
|
||||
def test_event_get(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must retrieve events
|
||||
"""
|
||||
event_mock = mocker.patch("ahriman.core.database.SQLite.event_get")
|
||||
local_client.event_get(EventType.PackageUpdated, package_ahriman.base, 1, 2)
|
||||
event_mock.assert_called_once_with(EventType.PackageUpdated, package_ahriman.base, 1, 2, local_client.repository_id)
|
||||
|
||||
|
||||
def test_package_changes_get(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must retrieve package changes
|
||||
|
@ -10,6 +10,7 @@ from ahriman.core.status.web_client import WebClient
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.changes import Changes
|
||||
from ahriman.models.dependencies import Dependencies
|
||||
from ahriman.models.event import Event, EventType
|
||||
from ahriman.models.internal_status import InternalStatus
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.package import Package
|
||||
@ -54,18 +55,12 @@ def test_dependencies_url(web_client: WebClient, package_ahriman: Package) -> No
|
||||
"/api/v1/packages/some%2Fpackage%25name/dependencies")
|
||||
|
||||
|
||||
def test__patches_url(web_client: WebClient, package_ahriman: Package) -> None:
|
||||
def test_event_url(web_client: WebClient) -> None:
|
||||
"""
|
||||
must generate changes url correctly
|
||||
must generate audit log url correctly
|
||||
"""
|
||||
assert web_client._patches_url(package_ahriman.base).startswith(web_client.address)
|
||||
assert web_client._patches_url(package_ahriman.base).endswith(f"/api/v1/packages/{package_ahriman.base}/patches")
|
||||
assert web_client._patches_url("some/package%name").endswith("/api/v1/packages/some%2Fpackage%25name/patches")
|
||||
|
||||
assert web_client._patches_url(package_ahriman.base, "var").endswith(
|
||||
f"/api/v1/packages/{package_ahriman.base}/patches/var")
|
||||
assert web_client._patches_url(package_ahriman.base, "some/variable%name").endswith(
|
||||
f"/api/v1/packages/{package_ahriman.base}/patches/some%2Fvariable%25name")
|
||||
assert web_client._events_url().startswith(web_client.address)
|
||||
assert web_client._events_url().endswith("/api/v1/events")
|
||||
|
||||
|
||||
def test_logs_url(web_client: WebClient, package_ahriman: Package) -> None:
|
||||
@ -89,6 +84,20 @@ def test_package_url(web_client: WebClient, package_ahriman: Package) -> None:
|
||||
assert web_client._package_url("some/package%name").endswith("/api/v1/packages/some%2Fpackage%25name")
|
||||
|
||||
|
||||
def test_patches_url(web_client: WebClient, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must generate changes url correctly
|
||||
"""
|
||||
assert web_client._patches_url(package_ahriman.base).startswith(web_client.address)
|
||||
assert web_client._patches_url(package_ahriman.base).endswith(f"/api/v1/packages/{package_ahriman.base}/patches")
|
||||
assert web_client._patches_url("some/package%name").endswith("/api/v1/packages/some%2Fpackage%25name/patches")
|
||||
|
||||
assert web_client._patches_url(package_ahriman.base, "var").endswith(
|
||||
f"/api/v1/packages/{package_ahriman.base}/patches/var")
|
||||
assert web_client._patches_url(package_ahriman.base, "some/variable%name").endswith(
|
||||
f"/api/v1/packages/{package_ahriman.base}/patches/some%2Fvariable%25name")
|
||||
|
||||
|
||||
def test_status_url(web_client: WebClient) -> None:
|
||||
"""
|
||||
must generate package status url correctly
|
||||
@ -97,6 +106,135 @@ def test_status_url(web_client: WebClient) -> None:
|
||||
assert web_client._status_url().endswith("/api/v1/status")
|
||||
|
||||
|
||||
def test_event_add(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create event
|
||||
"""
|
||||
event = Event(EventType.PackageUpdated, package_ahriman.base)
|
||||
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request")
|
||||
|
||||
web_client.event_add(event)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True),
|
||||
params=web_client.repository_id.query(), json=event.view())
|
||||
|
||||
|
||||
def test_event_add_failed(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during events creation
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
web_client.event_add(Event("", ""))
|
||||
|
||||
|
||||
def test_event_add_failed_http_error(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress HTTP exception happened during events creation
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
web_client.event_add(Event("", ""))
|
||||
|
||||
|
||||
def test_event_add_failed_suppress(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during events creaton and don't log
|
||||
"""
|
||||
web_client.suppress_errors = True
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
logging_mock = mocker.patch("logging.exception")
|
||||
|
||||
web_client.event_add(Event("", ""))
|
||||
logging_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_event_add_failed_http_error_suppress(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress HTTP exception happened during events creation and don't log
|
||||
"""
|
||||
web_client.suppress_errors = True
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
logging_mock = mocker.patch("logging.exception")
|
||||
|
||||
web_client.event_add(Event("", ""))
|
||||
logging_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_event_get(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get events
|
||||
"""
|
||||
event = Event(EventType.PackageUpdated, package_ahriman.base)
|
||||
response_obj = requests.Response()
|
||||
response_obj._content = json.dumps([event.view()]).encode("utf8")
|
||||
response_obj.status_code = 200
|
||||
|
||||
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=response_obj)
|
||||
|
||||
result = web_client.event_get(None, None)
|
||||
requests_mock.assert_called_once_with("GET", pytest.helpers.anyvar(str, True),
|
||||
params=web_client.repository_id.query() + [("limit", "-1"), ("offset", "0")])
|
||||
assert result == [event]
|
||||
|
||||
|
||||
def test_event_get_filter(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get events with filter
|
||||
"""
|
||||
response_obj = requests.Response()
|
||||
response_obj._content = json.dumps(Event("", "").view()).encode("utf8")
|
||||
response_obj.status_code = 200
|
||||
|
||||
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=response_obj)
|
||||
|
||||
web_client.event_get("event", "object", 1, 2)
|
||||
requests_mock.assert_called_once_with("GET", pytest.helpers.anyvar(str, True),
|
||||
params=web_client.repository_id.query() + [
|
||||
("limit", "1"),
|
||||
("offset", "2"),
|
||||
("event", "event"),
|
||||
("object_id", "object"),
|
||||
])
|
||||
|
||||
|
||||
def test_event_get_failed(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during events fetch
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
web_client.event_get(None, None)
|
||||
|
||||
|
||||
def test_event_get_failed_http_error(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress HTTP exception happened during events fetch
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
web_client.event_get(None, None)
|
||||
|
||||
|
||||
def test_event_get_failed_suppress(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during events fetch and don't log
|
||||
"""
|
||||
web_client.suppress_errors = True
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
logging_mock = mocker.patch("logging.exception")
|
||||
|
||||
web_client.event_get(None, None)
|
||||
logging_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_event_get_failed_http_error_suppress(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress HTTP exception happened during events fetch and don't log
|
||||
"""
|
||||
web_client.suppress_errors = True
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
logging_mock = mocker.patch("logging.exception")
|
||||
|
||||
web_client.event_get(None, None)
|
||||
logging_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_package_changes_get(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get changes
|
||||
|
17
tests/ahriman/models/test_event.py
Normal file
17
tests/ahriman/models/test_event.py
Normal file
@ -0,0 +1,17 @@
|
||||
from ahriman.models.event import Event, EventType
|
||||
|
||||
|
||||
def test_post_init() -> None:
|
||||
"""
|
||||
must remove replace empty dictionary
|
||||
"""
|
||||
assert Event("", "").data == {}
|
||||
assert isinstance(Event(str(EventType.PackageUpdated), "").event, EventType)
|
||||
|
||||
|
||||
def test_from_json_view() -> None:
|
||||
"""
|
||||
must construct and serialize event to json
|
||||
"""
|
||||
event = Event("event", "object", "message", {"key": "value"})
|
||||
assert Event.from_json(event.view()) == event
|
1
tests/ahriman/web/schemas/test_event_schema.py
Normal file
1
tests/ahriman/web/schemas/test_event_schema.py
Normal file
@ -0,0 +1 @@
|
||||
# schema testing goes in view class tests
|
1
tests/ahriman/web/schemas/test_event_search_schema.py
Normal file
1
tests/ahriman/web/schemas/test_event_search_schema.py
Normal file
@ -0,0 +1 @@
|
||||
# schema testing goes in view class tests
|
@ -0,0 +1,104 @@
|
||||
import pytest
|
||||
|
||||
from aiohttp.test_utils import TestClient
|
||||
|
||||
from ahriman.models.event import Event
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v1.auditlog.events import EventsView
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
"""
|
||||
must return correct permission for the request
|
||||
"""
|
||||
for method in ("GET", "POST"):
|
||||
request = pytest.helpers.request("", "", method)
|
||||
assert await EventsView.get_permission(request) == UserAccess.Full
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert EventsView.ROUTES == ["/api/v1/events"]
|
||||
|
||||
|
||||
async def test_get(client: TestClient) -> None:
|
||||
"""
|
||||
must return all events
|
||||
"""
|
||||
event1 = Event("event1", "object1", "message", {"key": "value"})
|
||||
event2 = Event("event2", "object2")
|
||||
await client.post("/api/v1/events", json=event1.view())
|
||||
await client.post("/api/v1/events", json=event2.view())
|
||||
response_schema = pytest.helpers.schema_response(EventsView.get)
|
||||
|
||||
response = await client.get("/api/v1/events")
|
||||
assert response.ok
|
||||
json = await response.json()
|
||||
assert not response_schema.validate(json, many=True)
|
||||
|
||||
events = [Event.from_json(event) for event in json]
|
||||
assert events == [event1, event2]
|
||||
|
||||
|
||||
async def test_get_with_pagination(client: TestClient) -> None:
|
||||
"""
|
||||
must get events with pagination
|
||||
"""
|
||||
event1 = Event("event1", "object1", "message", {"key": "value"})
|
||||
event2 = Event("event2", "object2")
|
||||
await client.post("/api/v1/events", json=event1.view())
|
||||
await client.post("/api/v1/events", json=event2.view())
|
||||
request_schema = pytest.helpers.schema_request(EventsView.get, location="querystring")
|
||||
response_schema = pytest.helpers.schema_response(EventsView.get)
|
||||
|
||||
payload = {"limit": 1, "offset": 1}
|
||||
assert not request_schema.validate(payload)
|
||||
response = await client.get("/api/v1/events", params=payload)
|
||||
assert response.status == 200
|
||||
|
||||
json = await response.json()
|
||||
assert not response_schema.validate(json, many=True)
|
||||
|
||||
assert [Event.from_json(event) for event in json] == [event2]
|
||||
|
||||
|
||||
async def test_get_bad_request(client: TestClient) -> None:
|
||||
"""
|
||||
must return bad request for invalid query parameters
|
||||
"""
|
||||
response_schema = pytest.helpers.schema_response(EventsView.get, code=400)
|
||||
|
||||
response = await client.get("/api/v1/events", params={"limit": "limit"})
|
||||
assert response.status == 400
|
||||
assert not response_schema.validate(await response.json())
|
||||
|
||||
response = await client.get("/api/v1/events", params={"offset": "offset"})
|
||||
assert response.status == 400
|
||||
assert not response_schema.validate(await response.json())
|
||||
|
||||
|
||||
async def test_post(client: TestClient) -> None:
|
||||
"""
|
||||
must create event
|
||||
"""
|
||||
event = Event("event1", "object1", "message", {"key": "value"})
|
||||
request_schema = pytest.helpers.schema_request(EventsView.post)
|
||||
|
||||
payload = event.view()
|
||||
assert not request_schema.validate(payload)
|
||||
|
||||
response = await client.post("/api/v1/events", json=payload)
|
||||
assert response.status == 204
|
||||
|
||||
|
||||
async def test_post_exception(client: TestClient) -> None:
|
||||
"""
|
||||
must raise exception on invalid payload
|
||||
"""
|
||||
response_schema = pytest.helpers.schema_response(EventsView.post, code=400)
|
||||
|
||||
response = await client.post("/api/v1/events", json={})
|
||||
assert response.status == 400
|
||||
assert not response_schema.validate(await response.json())
|
Reference in New Issue
Block a user