-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(messages): handle dto and wrapper
- Loading branch information
Showing
6 changed files
with
274 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
""" | ||
This module is used to define the ``MessageApi``, a python wrapper to interact with ``Messages`` in Novu. | ||
""" | ||
from typing import Dict, Optional, Union | ||
|
||
from novu.api.base import Api | ||
from novu.constants import MESSAGES_ENDPOINT | ||
from novu.dto.message import PaginatedMessageDto | ||
|
||
|
||
class MessageApi(Api): | ||
"""This class aims to handle all API methods around messages in Novu""" | ||
|
||
def __init__(self, url: Optional[str] = None, api_key: Optional[str] = None) -> None: | ||
super().__init__(url, api_key) | ||
|
||
self._message_url = f"{self._url}{MESSAGES_ENDPOINT}" | ||
|
||
def list( | ||
self, limit: int = 10, page: int = 0, channel: Optional[str] = None, subscriber_id: Optional[str] = None | ||
) -> PaginatedMessageDto: | ||
"""List messages | ||
Args: | ||
limit: The number of messages to fetch, defaults to 10 | ||
page: The page to fetch, defaults to 0 | ||
channel: The channel for the messages you wish to list. Defaults to None. | ||
subscriber_id: The subscriberId for the subscriber you like to list messages for | ||
Returns: | ||
Returned a paginated struct containing retrieved messages | ||
""" | ||
payload: Dict[str, Union[str, int]] = {"limit": limit, "page": page} | ||
|
||
if channel: | ||
payload["channel"] = channel | ||
if subscriber_id: | ||
payload["subscriberId"] = subscriber_id | ||
|
||
return PaginatedMessageDto.from_camel_case(self.handle_request("GET", self._message_url, payload=payload)) | ||
|
||
def delete(self, message_id: str) -> bool: | ||
"""Deletes a message entity from the Novu platform | ||
Args: | ||
message_id: The message ID to delete | ||
Returns: | ||
This function answer if the delete is a success by parsing the acknowledged field in response. | ||
""" | ||
return self.handle_request("DELETE", f"{self._message_url}/{message_id}")["data"].get("acknowledged", False) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
"""This module is used to gather all DTO definitions related to the Message resource in Novu""" | ||
import dataclasses | ||
from typing import List, Optional | ||
|
||
from novu.dto.base import CamelCaseDto, DtoIterableDescriptor | ||
from novu.enums import Channel, ProviderIdEnum | ||
|
||
|
||
@dataclasses.dataclass | ||
class MessageDto(CamelCaseDto["MessageDto"]): # pylint: disable=R0902 | ||
"""Definition of an event""" | ||
|
||
identifier: Optional[str] = None | ||
"""The message identifier""" | ||
|
||
_id: Optional[str] = None | ||
"""Message Notification ID in Novu internal storage system""" | ||
|
||
_template_id: Optional[str] = None | ||
"""Template ID in Novu internal storage system""" | ||
|
||
_environment_id: Optional[str] = None | ||
"""Environment ID in Novu internal storage system""" | ||
|
||
_message_template_id: Optional[str] = None | ||
"""Message Template ID in Novu internal storage system""" | ||
|
||
_organization_id: Optional[str] = None | ||
"""Organization ID in Novu internal storage system""" | ||
|
||
_subscriber_id: Optional[str] = None | ||
"""Subscriber ID in Novu internal storage system""" | ||
|
||
_job_id: Optional[str] = None | ||
"""Job ID in Novu internal storage system""" | ||
|
||
template_identifier: Optional[str] = None | ||
"""Template ID in Novu internal storage system""" | ||
|
||
email: Optional[str] = None | ||
"""Email of the subscriber triggered where the message has been sent""" | ||
|
||
subject: Optional[str] = None | ||
"""Subject of the email sent""" | ||
|
||
cta: Optional[dict] = None | ||
"""Definition of the call to action used on message""" | ||
|
||
channel: Optional[Channel] = None | ||
"""Channel used for the message""" | ||
|
||
content: Optional[str] = None | ||
"""Content of the message""" | ||
|
||
provider_id: Optional[ProviderIdEnum] = None | ||
"""Provider ID used for the message""" | ||
|
||
device_tokens: Optional[List[dict]] = None | ||
"""A list of device tokens used on the provider to send message""" | ||
|
||
seen: Optional[bool] = None | ||
"""If the message has been seen.""" | ||
|
||
read: Optional[bool] = None | ||
"""If the message has been read""" | ||
|
||
status: Optional[str] = None | ||
"""Status of the activity notification""" | ||
|
||
transaction_id: Optional[str] = None | ||
"""Transaction ID in Novu internal storage system""" | ||
|
||
payload: Optional[dict] = None | ||
"""Payload used during trigger""" | ||
|
||
created_at: Optional[str] = None | ||
updated_at: Optional[str] = None | ||
deleted: Optional[bool] = None | ||
|
||
last_read_date: Optional[str] = None | ||
"""Timestamp of the last read event registered""" | ||
|
||
last_seen_date: Optional[str] = None | ||
"""Timestamp of the last seen event registered""" | ||
|
||
|
||
@dataclasses.dataclass | ||
class PaginatedMessageDto(CamelCaseDto["PaginatedMessageDto"]): | ||
"""Paginated message definition""" | ||
|
||
page: int = 0 | ||
total_count: int = 0 | ||
page_size: int = 0 | ||
data: DtoIterableDescriptor[MessageDto] = DtoIterableDescriptor[MessageDto]( | ||
default_factory=list, item_cls=MessageDto | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
from unittest import TestCase, mock | ||
|
||
from novu.api import MessageApi | ||
from novu.config import NovuConfig | ||
from novu.dto.message import MessageDto, PaginatedMessageDto | ||
from novu.enums import Channel | ||
from tests.factories import MockResponse | ||
|
||
|
||
class MessageApiTests(TestCase): | ||
@classmethod | ||
def setUpClass(cls) -> None: | ||
NovuConfig.configure("sample.novu.com", "api-key") | ||
cls.api = MessageApi() | ||
|
||
cls.response_json = { | ||
"_id": "63daff4cc037e013fd82dadd", | ||
"_templateId": "63daff36c037e013fd82da05", | ||
"_environmentId": "63dafed97779f59258e38445", | ||
"_messageTemplateId": "63daff36c037e013fd82d9f4", | ||
"_notificationId": "63daff487779f59258e38b24", | ||
"_organizationId": "63dafed97779f59258e3843f", | ||
"_subscriberId": "63dafedbc037e013fd82d37a", | ||
"_jobId": "63daff4c7779f59258e38b3c", | ||
"templateIdentifier": "absences", | ||
"cta": {"action": {"buttons": []}}, | ||
"_feedId": None, | ||
"channel": "in_app", | ||
"content": "test", | ||
"deviceTokens": [], | ||
"seen": True, | ||
"read": True, | ||
"status": "sent", | ||
"transactionId": "aa287682-cb30-4a5f-a03a-f28f59c9d46d", | ||
"deleted": False, | ||
"createdAt": "2023-02-02T00:09:48.673Z", | ||
"updatedAt": "2023-02-02T00:10:21.544Z", | ||
"__v": 0, | ||
"lastReadDate": "2023-02-02T00:10:21.544Z", | ||
"lastSeenDate": "2023-02-02T00:10:21.544Z", | ||
} | ||
cls.maxDiff = None | ||
cls.expected_dto = MessageDto( | ||
identifier=None, | ||
_id="63daff4cc037e013fd82dadd", | ||
_template_id="63daff36c037e013fd82da05", | ||
_environment_id="63dafed97779f59258e38445", | ||
_message_template_id="63daff36c037e013fd82d9f4", | ||
_organization_id="63dafed97779f59258e3843f", | ||
_subscriber_id="63dafedbc037e013fd82d37a", | ||
_job_id="63daff4c7779f59258e38b3c", | ||
template_identifier="absences", | ||
email=None, | ||
subject=None, | ||
cta={"action": {"buttons": []}}, | ||
channel="in_app", | ||
content="test", | ||
provider_id=None, | ||
device_tokens=[], | ||
seen=True, | ||
read=True, | ||
status="sent", | ||
transaction_id="aa287682-cb30-4a5f-a03a-f28f59c9d46d", | ||
payload=None, | ||
created_at="2023-02-02T00:09:48.673Z", | ||
updated_at="2023-02-02T00:10:21.544Z", | ||
deleted=False, | ||
last_read_date="2023-02-02T00:10:21.544Z", | ||
last_seen_date="2023-02-02T00:10:21.544Z", | ||
) | ||
|
||
@mock.patch("requests.request") | ||
def test_list_messages(self, mock_request: mock.MagicMock) -> None: | ||
mock_request.return_value = MockResponse(200, {"data": [self.response_json]}) | ||
|
||
res = self.api.list() | ||
self.assertIsInstance(res, PaginatedMessageDto) | ||
self.assertEqual(list(res.data), [self.expected_dto]) | ||
|
||
mock_request.assert_called_once_with( | ||
method="GET", | ||
url="sample.novu.com/v1/messages", | ||
headers={"Authorization": "ApiKey api-key"}, | ||
json=None, | ||
params={"limit": 10, "page": 0}, | ||
timeout=5, | ||
) | ||
|
||
@mock.patch("requests.request") | ||
def test_list_messages_with_filters(self, mock_request: mock.MagicMock) -> None: | ||
mock_request.return_value = MockResponse(200, {"data": [self.response_json]}) | ||
|
||
res = self.api.list(10, 0, Channel.IN_APP.value, "63dafedbc037e013fd82d37a") | ||
self.assertIsInstance(res, PaginatedMessageDto) | ||
self.assertEqual(list(res.data), [self.expected_dto]) | ||
|
||
mock_request.assert_called_once_with( | ||
method="GET", | ||
url="sample.novu.com/v1/messages", | ||
headers={"Authorization": "ApiKey api-key"}, | ||
json=None, | ||
params={"limit": 10, "page": 0, "channel": "in_app", "subscriberId": "63dafedbc037e013fd82d37a"}, | ||
timeout=5, | ||
) | ||
|
||
@mock.patch("requests.request") | ||
def test_delete_message(self, mock_request: mock.MagicMock) -> None: | ||
mock_request.return_value = MockResponse(200, {"data": {"acknowledged": True, "status": "deleted"}}) | ||
|
||
res = self.api.delete("63e969fcb6729e21337e2360") | ||
self.assertIsInstance(res, bool) | ||
self.assertTrue(res) | ||
|
||
mock_request.assert_called_once_with( | ||
method="DELETE", | ||
url="sample.novu.com/v1/messages/63e969fcb6729e21337e2360", | ||
headers={"Authorization": "ApiKey api-key"}, | ||
json=None, | ||
params=None, | ||
timeout=5, | ||
) |