diff --git a/lago_python_client/customers/wallet_client.py b/lago_python_client/customers/wallet_client.py index 990466ae..d9ada870 100644 --- a/lago_python_client/customers/wallet_client.py +++ b/lago_python_client/customers/wallet_client.py @@ -13,6 +13,7 @@ ) from .wallets.metadata_client import CustomerWalletMetadataClient +from .wallets.alert_client import CustomerWalletAlertClient class CustomerWalletClient( @@ -37,3 +38,7 @@ def api_resource(self, customer_id: str) -> tuple[str]: @callable_cached_property def metadata(self) -> CustomerWalletMetadataClient: return CustomerWalletMetadataClient(self.base_url, self.api_key) + + @callable_cached_property + def alerts(self) -> CustomerWalletAlertClient: + return CustomerWalletAlertClient(self.base_url, self.api_key) diff --git a/lago_python_client/customers/wallets/alert_client.py b/lago_python_client/customers/wallets/alert_client.py new file mode 100644 index 00000000..30ef6f80 --- /dev/null +++ b/lago_python_client/customers/wallets/alert_client.py @@ -0,0 +1,28 @@ +from typing import ClassVar, Type + +from ...base_client import BaseClient +from ...models.alert import AlertResponse + +from ...mixins import ( + NestedCreateCommandMixin, + NestedUpdateCommandMixin, + NestedDestroyCommandMixin, + NestedFindCommandMixin, + NestedFindAllCommandMixin, +) + + +class CustomerWalletAlertClient( + NestedCreateCommandMixin[AlertResponse], + NestedUpdateCommandMixin[AlertResponse], + NestedDestroyCommandMixin[AlertResponse], + NestedFindCommandMixin[AlertResponse], + NestedFindAllCommandMixin[AlertResponse], + BaseClient, +): + API_RESOURCE: ClassVar[str] = "alerts" + RESPONSE_MODEL: ClassVar[Type[AlertResponse]] = AlertResponse + ROOT_NAME: ClassVar[str] = "alert" + + def api_resource(self, customer_id: str, wallet_code: str) -> tuple[str]: + return ("customers", customer_id, "wallets", wallet_code, "alerts") diff --git a/lago_python_client/models/alert.py b/lago_python_client/models/alert.py index 3add5117..eacd1845 100644 --- a/lago_python_client/models/alert.py +++ b/lago_python_client/models/alert.py @@ -15,11 +15,11 @@ class AlertThresholdList(BaseModel): class Alert(BaseModel): - alert_type: str - code: str + alert_type: Optional[str] + code: Optional[str] name: Optional[str] + thresholds: Optional[AlertThresholdList] billable_metric_code: Optional[str] - thresholds: AlertThresholdList class AlertsList(BaseModel): @@ -39,10 +39,13 @@ class AlertThresholdResponseList(BaseResponseModel): class AlertResponse(BaseResponseModel): lago_id: str lago_organization_id: str - external_subscription_id: str + external_subscription_id: Optional[str] + lago_wallet_id: Optional[str] + wallet_code: Optional[str] alert_type: str code: str name: Optional[str] + direction: Optional[str] previous_value: Optional[str] last_processed_at: Optional[str] thresholds: AlertThresholdResponseList diff --git a/tests/fixtures/wallet_alert.json b/tests/fixtures/wallet_alert.json new file mode 100644 index 00000000..ab7e76c2 --- /dev/null +++ b/tests/fixtures/wallet_alert.json @@ -0,0 +1,26 @@ +{ + "alert": { + "lago_id": "1a901a90-1a90-1a90-1a90-1a901a901a90", + "lago_organization_id": "1a901a90-1a90-1a90-1a90-1a901a901a90", + "external_subscription_id": null, + "external_customer_id": "customer_id", + "lago_wallet_id": "1a901a90-1a90-1a90-1a90-1a901a901a90", + "wallet_code": "wallet_code", + "code": "wallet_balance_alert", + "name": "Balance Amount Alert", + "alert_type": "wallet_balance_amount", + "direction": "increasing", + "previous_value": 1000, + "thresholds": [ + { + "code": "warn", + "value": 10000, + "recurring": false + } + ], + "billable_metric": null, + "created_at": "2025-03-20T10:00:00Z", + "last_processed_at": "2025-05-19T10:04:21Z" + } +} + diff --git a/tests/fixtures/wallet_alert_index.json b/tests/fixtures/wallet_alert_index.json new file mode 100644 index 00000000..acc1c613 --- /dev/null +++ b/tests/fixtures/wallet_alert_index.json @@ -0,0 +1,34 @@ +{ + "alerts": [ + { + "lago_id": "1a901a90-1a90-1a90-1a90-1a901a901a90", + "lago_organization_id": "1a901a90-1a90-1a90-1a90-1a901a901a90", + "external_subscription_id": null, + "external_customer_id": "cus_0987654321", + "lago_wallet_id": "1a901a90-1a90-1a90-1a90-1a901a901a90", + "wallet_code": "wallet_code", + "code": "wallet_balance_alert", + "name": "Balance Amount Alert", + "alert_type": "wallet_balance_amount", + "direction": "increasing", + "previous_value": 1000, + "thresholds": [ + { + "code": "warn", + "recurring": false, + "value": "99.0" + } + ], + "billable_metric": null, + "created_at": "2025-03-20T10:00:00Z", + "last_processed_at": "2025-05-19T10:04:21Z" + } + ], + "meta": { + "current_page": 1, + "next_page": 2, + "prev_page": null, + "total_pages": 4, + "total_count": 70 + } +} diff --git a/tests/test_customer_wallet_alert_client.py b/tests/test_customer_wallet_alert_client.py new file mode 100644 index 00000000..5da936a5 --- /dev/null +++ b/tests/test_customer_wallet_alert_client.py @@ -0,0 +1,194 @@ +import pytest +from pytest_httpx import HTTPXMock + +from lago_python_client.client import Client +from lago_python_client.exceptions import LagoApiError +from lago_python_client.models import ( + Alert, + AlertThreshold, + AlertThresholdList, +) + +from .utils.mixin import mock_response + + +def alert_object(): + threshold = AlertThreshold(code="warn", value=10000) + + return Alert( + alert_type="wallet_balance_amount", + code="wallet_balance_alert", + name="Balance Amount Alert", + thresholds=AlertThresholdList(__root__=[threshold]), + ) + + +def test_valid_create_customer_wallet_alert_request(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + + httpx_mock.add_response( + method="POST", + url="https://api.getlago.com/api/v1/customers/customer_id/wallets/wallet_code/alerts", + content=mock_response("wallet_alert"), + ) + response = client.customers.wallets.alerts.create("customer_id", "wallet_code", alert_object()) + + assert response.lago_id == "1a901a90-1a90-1a90-1a90-1a901a901a90" + assert response.lago_organization_id == "1a901a90-1a90-1a90-1a90-1a901a901a90" + assert response.external_subscription_id is None + assert response.lago_wallet_id == "1a901a90-1a90-1a90-1a90-1a901a901a90" + assert response.wallet_code == "wallet_code" + assert response.code == "wallet_balance_alert" + assert response.name == "Balance Amount Alert" + assert response.alert_type == "wallet_balance_amount" + assert response.direction == "increasing" + assert response.previous_value == "1000" + assert response.thresholds == AlertThresholdList( + __root__=[AlertThreshold(code="warn", value=10000, recurring=False)] + ) + assert response.billable_metric is None + + +def test_invalid_create_customer_wallet_alert_request(httpx_mock: HTTPXMock): + client = Client(api_key="invalid") + + httpx_mock.add_response( + method="POST", + url="https://api.getlago.com/api/v1/customers/customer_id/wallets/wallet_code/alerts", + status_code=401, + content=b"", + ) + + with pytest.raises(LagoApiError): + client.customers.wallets.alerts.create("customer_id", "wallet_code", alert_object()) + + +def test_valid_update_customer_wallet_alert_request(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + code = "alert-code" + + httpx_mock.add_response( + method="PUT", + url="https://api.getlago.com/api/v1/customers/customer_id/wallets/wallet_code/alerts/" + code, + content=mock_response("wallet_alert"), + ) + response = client.customers.wallets.alerts.update("customer_id", "wallet_code", code, alert_object()) + + assert response.lago_id == "1a901a90-1a90-1a90-1a90-1a901a901a90" + + +def test_invalid_update_customer_wallet_alert_request(httpx_mock: HTTPXMock): + client = Client(api_key="invalid") + code = "invalid" + + httpx_mock.add_response( + method="PUT", + url="https://api.getlago.com/api/v1/customers/customer_id/wallets/wallet_code/alerts/" + code, + status_code=401, + content=b"", + ) + + with pytest.raises(LagoApiError): + client.customers.wallets.alerts.update("customer_id", "wallet_code", code, alert_object()) + + +def test_valid_find_customer_wallet_alert_request(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + code = "alert-code" + + httpx_mock.add_response( + method="GET", + url="https://api.getlago.com/api/v1/customers/customer_id/wallets/wallet_code/alerts/" + code, + content=mock_response("wallet_alert"), + ) + response = client.customers.wallets.alerts.find("customer_id", "wallet_code", code) + + assert response.lago_id == "1a901a90-1a90-1a90-1a90-1a901a901a90" + + +def test_invalid_find_customer_wallet_alert_request(httpx_mock: HTTPXMock): + client = Client(api_key="invalid") + code = "invalid" + + httpx_mock.add_response( + method="GET", + url="https://api.getlago.com/api/v1/customers/customer_id/wallets/wallet_code/alerts/" + code, + status_code=404, + content=b"", + ) + + with pytest.raises(LagoApiError): + client.customers.wallets.alerts.find("customer_id", "wallet_code", code) + + +def test_valid_destroy_customer_wallet_alert_request(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + code = "alert-code" + + httpx_mock.add_response( + method="DELETE", + url="https://api.getlago.com/api/v1/customers/customer_id/wallets/wallet_code/alerts/" + code, + content=mock_response("wallet_alert"), + ) + response = client.customers.wallets.alerts.destroy("customer_id", "wallet_code", code) + + assert response.lago_id == "1a901a90-1a90-1a90-1a90-1a901a901a90" + + +def test_invalid_destroy_customer_wallet_alert_request(httpx_mock: HTTPXMock): + client = Client(api_key="invalid") + code = "invalid" + + httpx_mock.add_response( + method="DELETE", + url="https://api.getlago.com/api/v1/customers/customer_id/wallets/wallet_code/alerts/" + code, + status_code=404, + content=b"", + ) + + with pytest.raises(LagoApiError): + client.customers.wallets.alerts.destroy("customer_id", "wallet_code", code) + + +def test_valid_find_all_customer_wallet_alerts_request(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + + httpx_mock.add_response( + method="GET", + url="https://api.getlago.com/api/v1/customers/customer_id/wallets/wallet_code/alerts", + content=mock_response("wallet_alert_index"), + ) + response = client.customers.wallets.alerts.find_all("customer_id", "wallet_code") + + assert response["alerts"][0].lago_id == "1a901a90-1a90-1a90-1a90-1a901a901a90" + assert response["meta"]["current_page"] == 1 + + +def test_valid_find_all_customer_wallet_alerts_request_with_options(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + + httpx_mock.add_response( + method="GET", + url="https://api.getlago.com/api/v1/customers/customer_id/wallets/wallet_code/alerts?page=1&per_page=2", + content=mock_response("wallet_alert_index"), + ) + response = client.customers.wallets.alerts.find_all( + "customer_id", "wallet_code", options={"per_page": 2, "page": 1} + ) + + assert response["alerts"][0].lago_id == "1a901a90-1a90-1a90-1a90-1a901a901a90" + assert response["meta"]["current_page"] == 1 + + +def test_invalid_find_all_wallet_alerts_request(httpx_mock: HTTPXMock): + client = Client(api_key="invalid") + + httpx_mock.add_response( + method="GET", + url="https://api.getlago.com/api/v1/customers/customer_id/wallets/wallet_code/alerts", + status_code=404, + content=b"", + ) + + with pytest.raises(LagoApiError): + client.customers.wallets.alerts.find_all("customer_id", "wallet_code")