Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update subscriber.py #53

Merged
merged 1 commit into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions novu/dto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
from novu.dto.step_filter import StepFilterDto
from novu.dto.subscriber import (
PaginatedSubscriberDto,
SubscriberChannelSettingsCredentialsDto,
SubscriberChannelSettingsDto,
SubscriberDto,
SubscriberPreferenceChannelDto,
SubscriberPreferenceDto,
Expand Down Expand Up @@ -92,6 +94,8 @@
"PaginatedTopicDto",
"StepFilterDto",
"SubscriberDto",
"SubscriberChannelSettingsDto",
"SubscriberChannelSettingsCredentialsDto",
"SubscriberPreferenceChannelDto",
"SubscriberPreferenceDto",
"SubscriberPreferencePreferenceDto",
Expand Down
43 changes: 40 additions & 3 deletions novu/dto/subscriber.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""This module is used to gather all DTO definitions related to the Subscriber resource in Novu"""
import dataclasses
from typing import Optional
from typing import List, Optional

from novu.dto.base import CamelCaseDto, DtoDescriptor, DtoIterableDescriptor
from novu.enums.provider import ProviderIdEnum


@dataclasses.dataclass
Expand Down Expand Up @@ -70,11 +71,44 @@ class SubscriberPreferenceDto(CamelCaseDto["SubscriberPreferenceDto"]):
"""The identifiers of the template linked to the preferences and its criticality"""


@dataclasses.dataclass
class SubscriberChannelSettingsCredentialsDto(CamelCaseDto["SubscriberChannelSettingsCredentialsDto"]):
"""Credentials payload for the specified provider."""

webhook_url: str
"""Webhook url used by chat app integrations. The webhook should be obtained from the chat app provider"""

channel: str
"""Channel specification for Mattermost chat notifications"""

device_tokens: Optional[List[str]] = None
"""Contains an array of the subscriber device tokens for a given provider. Used on Push integrations"""


@dataclasses.dataclass
class SubscriberChannelSettingsDto(CamelCaseDto["SubscriberChannelSettingsDto"]):
"""Definition of channel settings for subscriber."""

provider_id: ProviderIdEnum
"""The provider identifier for the credentials"""

_integration_id: str
"""Id of the integration that is used for this channel"""

credentials: DtoDescriptor[SubscriberChannelSettingsCredentialsDto] = DtoDescriptor[
SubscriberChannelSettingsCredentialsDto
](item_cls=SubscriberChannelSettingsCredentialsDto)
"""Credentials of this channel"""

integration_identifier: Optional[str] = None
"""The integration identifier"""


@dataclasses.dataclass
class SubscriberDto(CamelCaseDto["SubscriberDto"]): # pylint: disable=R0902
"""Definition of subscriber"""

camel_case_fields = ["subscriber_id", "email", "first_name", "last_name", "phone", "avatar", "locale"]
camel_case_fields = ["subscriber_id", "email", "first_name", "last_name", "phone", "avatar", "locale", "channels"]
# Actually, only these fields are editable in Novu, so prevent any activity on others

subscriber_id: str
Expand Down Expand Up @@ -107,7 +141,10 @@ class SubscriberDto(CamelCaseDto["SubscriberDto"]): # pylint: disable=R0902
_organization_id: Optional[str] = None
"""Organization ID in Novu internal storage system"""

# TODO: add channels
channels: DtoIterableDescriptor[SubscriberChannelSettingsDto] = DtoIterableDescriptor[SubscriberChannelSettingsDto](
default_factory=list, item_cls=SubscriberChannelSettingsDto
)
"""Subscriber provider specific credentials"""

deleted: Optional[bool] = None
"""If the subscriber is deleted"""
Expand Down
78 changes: 77 additions & 1 deletion tests/api/test_subscriber.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
from novu.config import NovuConfig
from novu.dto.subscriber import (
PaginatedSubscriberDto,
SubscriberChannelSettingsCredentialsDto,
SubscriberChannelSettingsDto,
SubscriberDto,
SubscriberPreferenceChannelDto,
SubscriberPreferenceDto,
SubscriberPreferencePreferenceDto,
SubscriberPreferenceTemplateDto,
)
from novu.enums import Channel
from novu.enums import Channel, ChatProviderIdEnum
from tests.factories import MockResponse


Expand Down Expand Up @@ -50,6 +52,7 @@ def setUpClass(cls) -> None:
_id="63dafedbc037e013fd82d37a",
_environment_id="63dafed97779f59258e38445",
_organization_id="63dafed97779f59258e3843f",
channels=[],
deleted=False,
created_at="2023-02-02T00:07:55.459Z",
updated_at="2023-02-06T23:03:22.645Z",
Expand Down Expand Up @@ -134,6 +137,7 @@ def test_create_subscriber(self, mock_request: mock.MagicMock) -> None:
"phone": None,
"avatar": None,
"locale": None,
"channels": None,
},
params=None,
timeout=5,
Expand All @@ -156,6 +160,77 @@ def test_get_subscriber(self, mock_request: mock.MagicMock) -> None:
timeout=5,
)

@mock.patch("requests.request")
def test_get_subscriber_with_credentials_info(self, mock_request: mock.MagicMock) -> None:
mock_request.return_value = MockResponse(
200,
{
"data": {
"_id": "63dafedbc037e013fd82d37a",
"_organizationId": "63dafed97779f59258e3843f",
"_environmentId": "63dafed97779f59258e38445",
"subscriberId": "63dafed4117f8c850991ec4a",
"channels": [
{
"provider_id": "slack",
"_integration_id": "64f6d74be166fcd7f2751111",
"credentials": {"webhook_url": "TEST", "channel": "slack", "device_tokens": ["TEST"]},
"integration_identifier": None,
}
],
"deleted": False,
"createdAt": "2023-02-02T00:07:55.459Z",
"updatedAt": "2023-02-06T23:03:22.645Z",
"__v": 0,
"isOnline": False,
"email": "[email protected]",
"lastOnlineAt": "2023-02-06T23:03:22.645Z",
}
},
)

res = self.api.get("subscriber-id")
self.assertIsInstance(res, SubscriberDto)
self.assertEqual(
res,
SubscriberDto(
subscriber_id="63dafed4117f8c850991ec4a",
email="[email protected]",
first_name=None,
last_name=None,
phone=None,
avatar=None,
locale=None,
_id="63dafedbc037e013fd82d37a",
_environment_id="63dafed97779f59258e38445",
_organization_id="63dafed97779f59258e3843f",
channels=[
SubscriberChannelSettingsDto(
provider_id=ChatProviderIdEnum.SLACK,
_integration_id="64f6d74be166fcd7f2751111",
credentials=SubscriberChannelSettingsCredentialsDto(
webhook_url="TEST", channel="slack", device_tokens=["TEST"]
),
integration_identifier=None,
)
],
deleted=False,
created_at="2023-02-02T00:07:55.459Z",
updated_at="2023-02-06T23:03:22.645Z",
is_online=False,
last_online_at="2023-02-06T23:03:22.645Z",
),
)

mock_request.assert_called_once_with(
method="GET",
url="sample.novu.com/v1/subscribers/subscriber-id",
headers={"Authorization": "ApiKey api-key"},
json=None,
params=None,
timeout=5,
)

@mock.patch("requests.request")
def test_update_subscriber(self, mock_request: mock.MagicMock) -> None:
mock_request.return_value = MockResponse(200, self.response_get)
Expand All @@ -176,6 +251,7 @@ def test_update_subscriber(self, mock_request: mock.MagicMock) -> None:
"phone": "+33612345678",
"avatar": None,
"locale": None,
"channels": None,
},
params=None,
timeout=5,
Expand Down