From d09e05a66aabd4f5c77319faf4f6c2c876686c35 Mon Sep 17 00:00:00 2001 From: Mario Diniz Date: Wed, 7 May 2025 14:09:39 -0300 Subject: [PATCH] feat(audit_logs):activity logs client endpoints --- lago_python_client/activity_logs/__init__.py | 0 lago_python_client/activity_logs/clients.py | 18 ++++ lago_python_client/client.py | 5 + lago_python_client/models/__init__.py | 1 + lago_python_client/models/activity_log.py | 36 +++++++ tests/fixtures/activity_log.json | 21 ++++ tests/fixtures/activity_log_index.json | 49 +++++++++ tests/test_activity_logs_client.py | 100 +++++++++++++++++++ 8 files changed, 230 insertions(+) create mode 100644 lago_python_client/activity_logs/__init__.py create mode 100644 lago_python_client/activity_logs/clients.py create mode 100644 lago_python_client/models/activity_log.py create mode 100644 tests/fixtures/activity_log.json create mode 100644 tests/fixtures/activity_log_index.json create mode 100644 tests/test_activity_logs_client.py diff --git a/lago_python_client/activity_logs/__init__.py b/lago_python_client/activity_logs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lago_python_client/activity_logs/clients.py b/lago_python_client/activity_logs/clients.py new file mode 100644 index 00000000..346f6fe0 --- /dev/null +++ b/lago_python_client/activity_logs/clients.py @@ -0,0 +1,18 @@ +from typing import ClassVar, Type + +from ..base_client import BaseClient +from ..mixins import ( + FindAllCommandMixin, + FindCommandMixin, +) +from ..models.activity_log import ActivityLogResponse + + +class ActivityLogClient( + FindAllCommandMixin[ActivityLogResponse], + FindCommandMixin[ActivityLogResponse], + BaseClient, +): + API_RESOURCE: ClassVar[str] = "activity_logs" + RESPONSE_MODEL: ClassVar[Type[ActivityLogResponse]] = ActivityLogResponse + ROOT_NAME: ClassVar[str] = "activity_log" diff --git a/lago_python_client/client.py b/lago_python_client/client.py index 7b1e0fa0..2e897a09 100644 --- a/lago_python_client/client.py +++ b/lago_python_client/client.py @@ -24,6 +24,7 @@ from .wallets.clients import WalletClient, WalletTransactionClient from .webhooks.clients import WebhookClient from .webhook_endpoints.clients import WebhookEndpointClient +from .activity_logs.clients import ActivityLogClient try: from typing import Final @@ -159,3 +160,7 @@ def webhooks(self) -> WebhookClient: @callable_cached_property def webhook_endpoints(self) -> WebhookEndpointClient: return WebhookEndpointClient(self.base_api_url, self.api_key) + + @callable_cached_property + def activity_logs(self) -> ActivityLogClient: + return ActivityLogClient(self.base_api_url, self.api_key) diff --git a/lago_python_client/models/__init__.py b/lago_python_client/models/__init__.py index 56da14f4..2cc2abed 100644 --- a/lago_python_client/models/__init__.py +++ b/lago_python_client/models/__init__.py @@ -1,3 +1,4 @@ +from .activity_log import ActivityLog as ActivityLog, ActivityLogResponse as ActivityLogResponse from .applied_coupon import AppliedCoupon as AppliedCoupon from .billable_metric import ( BillableMetric as BillableMetric, diff --git a/lago_python_client/models/activity_log.py b/lago_python_client/models/activity_log.py new file mode 100644 index 00000000..fdcb6b6e --- /dev/null +++ b/lago_python_client/models/activity_log.py @@ -0,0 +1,36 @@ +from typing import Optional + +from lago_python_client.base_model import BaseModel + +from ..base_model import BaseResponseModel + + +class ActivityLog(BaseModel): + activity_type: str + activity_object: Optional[dict] + activity_object_changes: Optional[dict] + activity_source: str + activity_type: str + logged_at: str + resource_type: str + created_at: str + activity_id: str + external_customer_id: Optional[str] + external_subscription_id: Optional[str] + resource_id: str + user_email: Optional[str] + + +class ActivityLogResponse(BaseResponseModel): + activity_id: str + activity_type: str + activity_source: str + activity_object: Optional[dict] + activity_object_changes: Optional[dict] + user_email: Optional[str] + resource_id: str + resource_type: str + external_customer_id: Optional[str] + external_subscription_id: Optional[str] + logged_at: str + created_at: str diff --git a/tests/fixtures/activity_log.json b/tests/fixtures/activity_log.json new file mode 100644 index 00000000..840a6596 --- /dev/null +++ b/tests/fixtures/activity_log.json @@ -0,0 +1,21 @@ +{ + "activity_log": { + "activity_id": "1262046f-ea6e-423b-8bf7-3a985232f91b", + "activity_type": "billable_metric.created", + "activity_source": "api", + "activity_object": { + "foo": "bar", + "baz": "qux" + }, + "activity_object_changes": { + "foo": "bar" + }, + "user_email": "duane.wolf@goodwin-upton.example", + "resource_id": "fd417abf-4273-42bf-bf93-fc3f93d4e987", + "resource_type": "BillableMetric", + "external_customer_id": "0c60aa34-74aa-470d-9d9c-409c79d05d56", + "external_subscription_id": "b0a3fa29-5ba8-4363-b45e-0ea4c700acf8", + "logged_at": "2025-05-05T15:06:21Z", + "created_at": "2025-05-05T15:06:21Z" + } +} diff --git a/tests/fixtures/activity_log_index.json b/tests/fixtures/activity_log_index.json new file mode 100644 index 00000000..076ec872 --- /dev/null +++ b/tests/fixtures/activity_log_index.json @@ -0,0 +1,49 @@ +{ + "activity_logs": [ + { + "activity_id": "1262046f-ea6e-423b-8bf7-3a985232f91b", + "activity_type": "billable_metric.created", + "activity_source": "api", + "activity_object": { + "foo": "bar", + "baz": "qux" + }, + "activity_object_changes": { + "foo": "bar" + }, + "user_email": "duane.wolf@goodwin-upton.example", + "resource_id": "fd417abf-4273-42bf-bf93-fc3f93d4e987", + "resource_type": "BillableMetric", + "external_customer_id": "0c60aa34-74aa-470d-9d9c-409c79d05d56", + "external_subscription_id": "b0a3fa29-5ba8-4363-b45e-0ea4c700acf8", + "logged_at": "2025-05-05T15:06:21Z", + "created_at": "2025-05-05T15:06:21Z" + }, + { + "activity_id": "6744ddec-3516-4e26-9c7e-dc3c30fc4e80", + "activity_type": "plan.created", + "activity_source": "api", + "activity_object": { + "foo": "bar-2", + "baz": "qux-2" + }, + "activity_object_changes": { + "foo": "bar-2" + }, + "user_email": "markus@deckow.example", + "resource_id": "010f1c2f-b86e-447e-8a79-e4d8cd3f8e96", + "resource_type": "Plan", + "external_customer_id": "8f326a03-852b-408f-ba5b-d633420b59d6", + "external_subscription_id": "8bdbe7c8-2817-4d93-b264-ce8417f09b2a", + "logged_at": "2025-05-05T15:33:50Z", + "created_at": "2025-05-05T15:33:50Z" + } + ], + "meta": { + "current_page": 1, + "next_page": 2, + "prev_page": null, + "total_pages": 8, + "total_count": 73 + } +} diff --git a/tests/test_activity_logs_client.py b/tests/test_activity_logs_client.py new file mode 100644 index 00000000..d787369e --- /dev/null +++ b/tests/test_activity_logs_client.py @@ -0,0 +1,100 @@ +import os + +import pytest +from pytest_httpx import HTTPXMock + +from urllib.parse import urlencode + +from lago_python_client.client import Client +from lago_python_client.exceptions import LagoApiError + +ENDPOINT = "https://api.getlago.com/api/v1/activity_logs" + + +def mock_response(fixture_path): + this_dir = os.path.dirname(os.path.abspath(__file__)) + data_path = os.path.join(this_dir, fixture_path) + + with open(data_path, "rb") as response: + return response.read() + + +def test_valid_find_activity_log_request(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + activity_id = "1262046f-ea6e-423b-8bf7-3a985232f91b" + + httpx_mock.add_response( + method="GET", + url=ENDPOINT + f"/{activity_id}", + content=mock_response("fixtures/activity_log.json"), + ) + response = client.activity_logs.find(activity_id) + + assert response.activity_id == activity_id + assert response.activity_type == "billable_metric.created" + + +def test_invalid_find_activity_log_request(httpx_mock: HTTPXMock): + client = Client(api_key="invalid") + activity_id = "invalid" + + httpx_mock.add_response( + method="GET", + url=ENDPOINT + f"/{activity_id}", + status_code=404, + content=b"", + ) + + with pytest.raises(LagoApiError): + client.activity_logs.find(activity_id) + + +def test_valid_find_all_activity_log_request(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + + httpx_mock.add_response( + method="GET", + url=ENDPOINT, + content=mock_response("fixtures/activity_log_index.json"), + ) + response = client.activity_logs.find_all() + + assert response["activity_logs"][0].activity_id == "1262046f-ea6e-423b-8bf7-3a985232f91b" + assert response["activity_logs"][1].activity_id == "6744ddec-3516-4e26-9c7e-dc3c30fc4e80" + assert response["meta"]["current_page"] == 1 + + +def test_valid_find_all_activity_log_request_with_options(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + options = {"per_page": 1, "page": 1} + + httpx_mock.add_response( + method="GET", + url=ENDPOINT + f"?{urlencode(options)}", + content=mock_response("fixtures/activity_log_index.json"), + ) + response = client.activity_logs.find_all(options) + + assert response["activity_logs"][1].activity_id == "6744ddec-3516-4e26-9c7e-dc3c30fc4e80" + assert response["meta"]["current_page"] == 1 + + +def test_invalid_find_all_activity_log_request(httpx_mock: HTTPXMock): + client = Client(api_key="invalid") + + httpx_mock.add_response( + method="GET", + url=ENDPOINT, + status_code=404, + content=b"", + ) + + with pytest.raises(LagoApiError): + client.activity_logs.find_all() + + +def test_invalid_operations_for_activity_log_request(): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + assert not hasattr(client.activity_logs, "create") + assert not hasattr(client.activity_logs, "update") + assert not hasattr(client.activity_logs, "destroy")