From 36cf46b453e9a858f793e037b1635f1bf8759368 Mon Sep 17 00:00:00 2001 From: Narayana Shanbhogh Date: Sat, 23 May 2026 14:56:44 +0530 Subject: [PATCH] Add whatsapp_templates.create, whatsapp_templates.update, whatsapp_templates.get, whatsapp_templates.list, whatsapp_templates.delete, verify_apps.create, verify_apps.list, verify_apps.list_templates, verify_apps.get, verify_apps.update, verify_apps.delete, rcs_capability.check, rcs_assistant_events.create, verify_session.create, messages.create, messages.list, messages.get, verify_apps.create - whatsapp_templates.create (POST /v1/Account/{auth_id}/WhatsApp/Template/{waba_id}/) - whatsapp_templates.update (POST /v1/Account/{auth_id}/WhatsApp/Template/{waba_id}/{template_id}/) - whatsapp_templates.get (GET /v1/Account/{auth_id}/WhatsApp/Template/{waba_id}/{template_id}/) - whatsapp_templates.list (GET /v1/Account/{auth_id}/WhatsApp/Template/{waba_id}/) - whatsapp_templates.delete (DELETE /v1/Account/{auth_id}/WhatsApp/Template/{waba_id}/{template_id}/) - verify_apps.create (POST /v1/Account/{auth_id}/Verify/App/) - verify_apps.list (GET /v1/Account/{auth_id}/Verify/App/) - verify_apps.list_templates (GET /v1/Account/{auth_id}/Verify/App/templates/) - verify_apps.get (GET /v1/Account/{auth_id}/Verify/App/{app_uuid}/) - verify_apps.update (POST /v1/Account/{auth_id}/Verify/App/{app_uuid}/) - verify_apps.delete (DELETE /v1/Account/{auth_id}/Verify/App/{app_uuid}/) - rcs_capability.check (GET /v1/Account/{auth_id}/RCS/Capability/) - rcs_assistant_events.create (POST /v1/Account/{auth_id}/RCS/AssistantEvents/) - verify_session.create (POST /v1/Account/{auth_id}/Verify/Session/) - messages.create (POST /v1/Account/{auth_id}/Message/) - messages.list (GET /v1/Account/{auth_id}/Message/) - messages.get (GET /v1/Account/{auth_id}/Message/{record_id}/) - verify_apps.create (POST /v1/Account/{auth_id}/Verify/App/) --- CHANGELOG.md | 23 ++ examples/rcs_assistant_events_create.py | 8 + examples/rcs_capability_check.py | 18 ++ examples/verify_apps_create.py | 22 ++ examples/verify_apps_delete.py | 7 + examples/verify_apps_get.py | 7 + examples/verify_apps_list.py | 10 + examples/verify_apps_list_templates.py | 7 + examples/verify_apps_update.py | 12 + examples/verify_session_create.py | 27 ++ examples/whatsapp_templates_create.py | 23 ++ examples/whatsapp_templates_delete.py | 15 ++ examples/whatsapp_templates_get.py | 14 + examples/whatsapp_templates_list.py | 16 ++ examples/whatsapp_templates_update.py | 21 ++ plivo/resources/messages.py | 14 +- plivo/resources/rcs.py | 31 +++ plivo/resources/rcs_assistant_events.py | 27 ++ plivo/resources/verify_apps.py | 258 +++++++++++++++++++ plivo/resources/verify_session.py | 76 ++++++ plivo/resources/whatsapp_templates.py | 125 +++++++++ setup.py | 2 +- tests/resources/test_messages.py | 22 +- tests/resources/test_rcs.py | 58 +++++ tests/resources/test_rcs_assistant_events.py | 30 +++ tests/resources/test_verify_apps.py | 154 +++++++++++ tests/resources/test_verify_session.py | 71 +++++ tests/resources/test_whatsapp_templates.py | 126 +++++++++ 28 files changed, 1216 insertions(+), 8 deletions(-) create mode 100644 examples/rcs_assistant_events_create.py create mode 100644 examples/rcs_capability_check.py create mode 100644 examples/verify_apps_create.py create mode 100644 examples/verify_apps_delete.py create mode 100644 examples/verify_apps_get.py create mode 100644 examples/verify_apps_list.py create mode 100644 examples/verify_apps_list_templates.py create mode 100644 examples/verify_apps_update.py create mode 100644 examples/verify_session_create.py create mode 100644 examples/whatsapp_templates_create.py create mode 100644 examples/whatsapp_templates_delete.py create mode 100644 examples/whatsapp_templates_get.py create mode 100644 examples/whatsapp_templates_list.py create mode 100644 examples/whatsapp_templates_update.py create mode 100644 plivo/resources/rcs.py create mode 100644 plivo/resources/rcs_assistant_events.py create mode 100644 plivo/resources/verify_apps.py create mode 100644 plivo/resources/verify_session.py create mode 100644 plivo/resources/whatsapp_templates.py create mode 100644 tests/resources/test_rcs.py create mode 100644 tests/resources/test_rcs_assistant_events.py create mode 100644 tests/resources/test_verify_apps.py create mode 100644 tests/resources/test_verify_session.py create mode 100644 tests/resources/test_whatsapp_templates.py diff --git a/CHANGELOG.md b/CHANGELOG.md index b3bc619d..ff9daf55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,27 @@ # Change Log +## [4.61.0](https://github.com/plivo/plivo-python/tree/v4.61.0) (2026-05-23) +**Feature - whatsapp_templates, verify_apps, rcs_capability, rcs_assistant_events, verify_session, messages API updates** +- Added `waba_id`, `name`, `category`, `language`, `components`, `allow_category_change` parameters to whatsapp_templates.create (POST /v1/Account/{auth_id}/WhatsApp/Template/{waba_id}/) +- Added `waba_id`, `template_id`, `name`, `category`, `language`, `components`, `allow_category_change` parameters to whatsapp_templates.update (POST /v1/Account/{auth_id}/WhatsApp/Template/{waba_id}/{template_id}/) +- Added `waba_id`, `template_id` parameters to whatsapp_templates.get (GET /v1/Account/{auth_id}/WhatsApp/Template/{waba_id}/{template_id}/) +- Added `waba_id`, `template_name`, `limit`, `offset` parameters to whatsapp_templates.list (GET /v1/Account/{auth_id}/WhatsApp/Template/{waba_id}/) +- Added `waba_id`, `template_id`, `name` parameters to whatsapp_templates.delete (DELETE /v1/Account/{auth_id}/WhatsApp/Template/{waba_id}/{template_id}/) +- Added `name`, `brand_name`, `otp_type`, `otp_length`, `otp_expiry`, `otp_attempts`, `max_validation_attempts`, `sms_channel`, `voice_channel`, `wa_channel`, `waba_id`, `waba_phone_number`, `waba_template_id`, `template_uuid`, `is_default`, `message_redaction`, `enable_fraudshield`, `fs_protection_level`, `customer_app_hash`, `number_pool` parameters to verify_apps.create (POST /v1/Account/{auth_id}/Verify/App/) +- Added `name`, `subaccount`, `limit`, `offset`, `created_at`, `created_at__lt`, `created_at__lte`, `created_at__gt`, `created_at__gte` parameters to verify_apps.list (GET /v1/Account/{auth_id}/Verify/App/) +- GET /v1/Account/{auth_id}/Verify/App/templates/ — verify_apps.list_templates. List default Verify templates available to the account. +- Added `app_uuid` required parameter to verify_apps.get (GET /v1/Account/{auth_id}/Verify/App/{app_uuid}/) +- Added `app_uuid`, `name`, `brand_name`, `otp_type`, `otp_length`, `otp_expiry`, `otp_attempts`, `max_validation_attempts`, `sms_channel`, `voice_channel`, `wa_channel`, `waba_id`, `waba_phone_number`, `waba_template_id`, `template_uuid`, `is_default`, `message_redaction`, `enable_fraudshield`, `fs_protection_level`, `customer_app_hash`, `client` parameters to verify_apps.update (POST /v1/Account/{auth_id}/Verify/App/{app_uuid}/) +- Added `app_uuid` required parameter to verify_apps.delete (DELETE /v1/Account/{auth_id}/Verify/App/{app_uuid}/) +- Added `phone_number`, `agent_uuid` parameters to rcs_capability.check (GET /v1/Account/{auth_id}/RCS/Capability/) +- POST /v1/Account/{auth_id}/RCS/AssistantEvents/ — rcs_assistant_events.create. Send RCS assistant events. +- Added `app_hash`, `brand_name`, `code_length`, `dlt_entity_id`, `dlt_sender_id`, `dlt_template_category`, `dlt_template_id`, `dlt_text`, `dtmf`, `fraud_check`, `text` parameters to verify_session.create (POST /v1/Account/{auth_id}/Verify/Session/) +- Added `content_message` optional parameter to messages.create (POST /v1/Account/{auth_id}/Message/) +- GET /v1/Account/{auth_id}/Message/ — messages.list. Add error_message, message_sent_time, and message_updated_time fields to the list messages response. +- GET /v1/Account/{auth_id}/Message/{record_id}/ — messages.get. Add error_message, message_sent_time, and message_updated_time fields to the get message response. +- Added `number_pool` optional parameter to verify_apps.create (POST /v1/Account/{auth_id}/Verify/App/) + +_Source: plivo/api-messaging#630_ + ## [4.60.1](https://github.com/plivo/plivo-python/tree/v4.60.1) (2026-04-17) **Bug Fix - PhoneNumber Compliance API** - Fixed Requirements.get() sending None values as query params when not provided diff --git a/examples/rcs_assistant_events_create.py b/examples/rcs_assistant_events_create.py new file mode 100644 index 00000000..c2394b7e --- /dev/null +++ b/examples/rcs_assistant_events_create.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +import plivo + +client = plivo.RestClient(auth_id='YOUR_AUTH_ID', auth_token='YOUR_AUTH_TOKEN') + +response = client.rcs_assistant_events.create() + +print(response) \ No newline at end of file diff --git a/examples/rcs_capability_check.py b/examples/rcs_capability_check.py new file mode 100644 index 00000000..6978e1b2 --- /dev/null +++ b/examples/rcs_capability_check.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +import plivo + +client = plivo.RestClient(auth_id='YOUR_AUTH_ID', auth_token='YOUR_AUTH_TOKEN') + +# Check if a phone number is RCS-enabled +response = client.rcs_capability.check( + phone_number='+14151234567', + agent_uuid='your-agent-uuid', # optional +) + +print('API ID:', response.api_id) +print('Phone Number:', response.phone_number) +print('Is RCS Capable:', response.is_capable) +print('Features:', response.features) +print('Message:', response.message) +if response.error: + print('Error:', response.error) \ No newline at end of file diff --git a/examples/verify_apps_create.py b/examples/verify_apps_create.py new file mode 100644 index 00000000..166a1bb1 --- /dev/null +++ b/examples/verify_apps_create.py @@ -0,0 +1,22 @@ +import plivo + +client = plivo.RestClient(auth_id='YOUR_AUTH_ID', auth_token='YOUR_AUTH_TOKEN') + +response = client.verify_apps.create( + name='MyVerifyApp', + brand_name='MyBrand', + otp_type='numeric', + otp_length=6, + otp_expiry=300, + otp_attempts=3, + max_validation_attempts=5, + sms_channel=True, + voice_channel=False, + wa_channel=False, + is_default=False, + message_redaction=False, + enable_fraudshield=False, + number_pool='my-number-pool', +) + +print(response) \ No newline at end of file diff --git a/examples/verify_apps_delete.py b/examples/verify_apps_delete.py new file mode 100644 index 00000000..839e7c67 --- /dev/null +++ b/examples/verify_apps_delete.py @@ -0,0 +1,7 @@ +import plivo + +client = plivo.RestClient(auth_id='YOUR_AUTH_ID', auth_token='YOUR_AUTH_TOKEN') + +response = client.verify_apps.delete(app_uuid='your-app-uuid') + +print(response) \ No newline at end of file diff --git a/examples/verify_apps_get.py b/examples/verify_apps_get.py new file mode 100644 index 00000000..4f0da6a7 --- /dev/null +++ b/examples/verify_apps_get.py @@ -0,0 +1,7 @@ +import plivo + +client = plivo.RestClient(auth_id='YOUR_AUTH_ID', auth_token='YOUR_AUTH_TOKEN') + +response = client.verify_apps.get(app_uuid='your-app-uuid') + +print(response) \ No newline at end of file diff --git a/examples/verify_apps_list.py b/examples/verify_apps_list.py new file mode 100644 index 00000000..42c09f55 --- /dev/null +++ b/examples/verify_apps_list.py @@ -0,0 +1,10 @@ +import plivo + +client = plivo.RestClient(auth_id='YOUR_AUTH_ID', auth_token='YOUR_AUTH_TOKEN') + +response = client.verify_apps.list( + limit=20, + offset=0, +) + +print(response) \ No newline at end of file diff --git a/examples/verify_apps_list_templates.py b/examples/verify_apps_list_templates.py new file mode 100644 index 00000000..e9b0f672 --- /dev/null +++ b/examples/verify_apps_list_templates.py @@ -0,0 +1,7 @@ +import plivo + +client = plivo.RestClient(auth_id='YOUR_AUTH_ID', auth_token='YOUR_AUTH_TOKEN') + +response = client.verify_apps.list_templates() + +print(response) \ No newline at end of file diff --git a/examples/verify_apps_update.py b/examples/verify_apps_update.py new file mode 100644 index 00000000..45c5340d --- /dev/null +++ b/examples/verify_apps_update.py @@ -0,0 +1,12 @@ +import plivo + +client = plivo.RestClient(auth_id='YOUR_AUTH_ID', auth_token='YOUR_AUTH_TOKEN') + +response = client.verify_apps.update( + app_uuid='your-app-uuid', + name='UpdatedAppName', + otp_length=4, + sms_channel=True, +) + +print(response) \ No newline at end of file diff --git a/examples/verify_session_create.py b/examples/verify_session_create.py new file mode 100644 index 00000000..ac1c94f5 --- /dev/null +++ b/examples/verify_session_create.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +""" +Example: Create a Verify Session (generate OTP) +""" +import plivo + +client = plivo.RestClient(auth_id='YOUR_AUTH_ID', auth_token='YOUR_AUTH_TOKEN') + +# Minimal creation — no optional params required +response = client.verify_session.create() +print(response) + +# Full creation with all optional params +response = client.verify_session.create( + app_hash='android_app_hash_value', + brand_name='MyBrand', + code_length=6, + dlt_entity_id='dlt_entity_id_value', + dlt_sender_id='dlt_sender_id_value', + dlt_template_category='transactional', + dlt_template_id='dlt_template_id_value', + dlt_text='Your OTP is {otp}', + dtmf=1, + fraud_check='medium', + text='Your OTP is {otp}. It is valid for 10 minutes.', +) +print(response) \ No newline at end of file diff --git a/examples/whatsapp_templates_create.py b/examples/whatsapp_templates_create.py new file mode 100644 index 00000000..0942cb17 --- /dev/null +++ b/examples/whatsapp_templates_create.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +""" +Example: Create a WhatsApp message template. +""" +import plivo + +client = plivo.RestClient(auth_id='YOUR_AUTH_ID', auth_token='YOUR_AUTH_TOKEN') + +response = client.whatsapp_templates.create( + waba_id='', + name='my_template', + category='MARKETING', + language='en_US', + components=[ + { + 'type': 'BODY', + 'text': 'Hello, this is a test template.', + } + ], + allow_category_change=True, +) + +print(response) \ No newline at end of file diff --git a/examples/whatsapp_templates_delete.py b/examples/whatsapp_templates_delete.py new file mode 100644 index 00000000..c033e1dd --- /dev/null +++ b/examples/whatsapp_templates_delete.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +""" +Example: Delete a WhatsApp template by ID and name. +""" +import plivo + +client = plivo.RestClient(auth_id='YOUR_AUTH_ID', auth_token='YOUR_AUTH_TOKEN') + +response = client.whatsapp_templates.delete( + waba_id='', + template_id='', + name='my_template', +) + +print(response) \ No newline at end of file diff --git a/examples/whatsapp_templates_get.py b/examples/whatsapp_templates_get.py new file mode 100644 index 00000000..f1989100 --- /dev/null +++ b/examples/whatsapp_templates_get.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +""" +Example: Retrieve a WhatsApp template by its ID. +""" +import plivo + +client = plivo.RestClient(auth_id='YOUR_AUTH_ID', auth_token='YOUR_AUTH_TOKEN') + +response = client.whatsapp_templates.get( + waba_id='', + template_id='', +) + +print(response) \ No newline at end of file diff --git a/examples/whatsapp_templates_list.py b/examples/whatsapp_templates_list.py new file mode 100644 index 00000000..c90c7232 --- /dev/null +++ b/examples/whatsapp_templates_list.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +""" +Example: List WhatsApp templates for a given WABA. +""" +import plivo + +client = plivo.RestClient(auth_id='YOUR_AUTH_ID', auth_token='YOUR_AUTH_TOKEN') + +response = client.whatsapp_templates.list( + waba_id='', + template_name='my_template', + limit=20, + offset=0, +) + +print(response) \ No newline at end of file diff --git a/examples/whatsapp_templates_update.py b/examples/whatsapp_templates_update.py new file mode 100644 index 00000000..8b23c562 --- /dev/null +++ b/examples/whatsapp_templates_update.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +""" +Example: Update an existing WhatsApp message template. +""" +import plivo + +client = plivo.RestClient(auth_id='YOUR_AUTH_ID', auth_token='YOUR_AUTH_TOKEN') + +response = client.whatsapp_templates.update( + waba_id='', + template_id='', + category='UTILITY', + components=[ + { + 'type': 'BODY', + 'text': 'Updated template body.', + } + ], +) + +print(response) \ No newline at end of file diff --git a/plivo/resources/messages.py b/plivo/resources/messages.py index 78692369..65dfb337 100644 --- a/plivo/resources/messages.py +++ b/plivo/resources/messages.py @@ -32,7 +32,7 @@ class Messages(PlivoResourceInterface): dst=[is_iterable(of_type(six.text_type), '<')], text=[optional(of_type(six.text_type))], type_=[ - optional(all_of(of_type(six.text_type), is_in(('sms', 'mms', 'whatsapp'))))], + optional(all_of(of_type(six.text_type), is_in(('sms', 'mms', 'whatsapp', 'rcs'))))], url=[optional(is_url())], method=[optional(of_type(six.text_type))], log=[optional(of_type_exact(str))], @@ -46,7 +46,8 @@ class Messages(PlivoResourceInterface): location=[optional(is_location())], dlt_entity_id=[optional(of_type(six.text_type))], dlt_template_id=[optional(of_type(six.text_type))], - dlt_template_category=[optional(of_type(six.text_type))] + dlt_template_category=[optional(of_type(six.text_type))], + content_message=[optional(of_type_exact(dict))], ) def create(self, dst, @@ -60,13 +61,14 @@ def create(self, powerpack_uuid=None, media_urls=None, media_ids=None, - message_expiry=None, + message_expiry=None, template=None, interactive=None, location=None, dlt_entity_id=None, dlt_template_id=None, - dlt_template_category=None): + dlt_template_category=None, + content_message=None): if src in dst.split('<'): raise ValidationError( 'destination number cannot be same as source number') @@ -87,7 +89,7 @@ def create(self, 'src parameter not present' ) if template is not None: - template = template.__dict__ + template = template.__dict__ if interactive is not None: interactive = interactive.__dict__ if location is not None: @@ -161,4 +163,4 @@ def list(self, 'GET', ('Message', ), to_param_dict(self.list, locals()), response_type=ListMessagesResponseObject, - objects_type=Message) + objects_type=Message) \ No newline at end of file diff --git a/plivo/resources/rcs.py b/plivo/resources/rcs.py new file mode 100644 index 00000000..1e536dfb --- /dev/null +++ b/plivo/resources/rcs.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from plivo.utils.validators import * + +from ..base import PlivoResource, PlivoResourceInterface, ResponseObject +from ..exceptions import * +from ..utils import * + + +class RcsCapabilityResponse(ResponseObject): + def __init__(self, client, dct): + super(RcsCapabilityResponse, self).__init__(dct) + self.api_id = dct.get('api_id', None) + self.phone_number = dct.get('phone_number', None) + self.is_capable = dct.get('is_capable', None) + self.features = dct.get('features', None) + self.message = dct.get('message', None) + self.error = dct.get('error', None) + + +class RcsCapability(PlivoResourceInterface): + @validate_args( + phone_number=[of_type(six.text_type)], + agent_uuid=[optional(of_type(six.text_type))], + ) + def check(self, phone_number, agent_uuid=None): + return self.client.request( + 'GET', + ('RCS', 'Capability'), + to_param_dict(self.check, locals()), + response_type=RcsCapabilityResponse, + ) \ No newline at end of file diff --git a/plivo/resources/rcs_assistant_events.py b/plivo/resources/rcs_assistant_events.py new file mode 100644 index 00000000..d173fe15 --- /dev/null +++ b/plivo/resources/rcs_assistant_events.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +from plivo.utils.validators import * + +from ..base import PlivoResource, PlivoResourceInterface +from ..exceptions import * +from ..utils import * + + +class RcsAssistantEvent(PlivoResource): + _name = 'RcsAssistantEvent' + _identifier_string = 'api_id' + + def delete(self): + raise InvalidRequestError('Cannot delete an RcsAssistantEvent resource') + + def update(self): + raise InvalidRequestError('Cannot update an RcsAssistantEvent resource') + + +class RcsAssistantEvents(PlivoResourceInterface): + _resource_type = RcsAssistantEvent + + def create(self, **kwargs): + return self.client.request( + 'POST', ('RCS', 'AssistantEvents'), + to_param_dict(self.create, locals()), + ) \ No newline at end of file diff --git a/plivo/resources/verify_apps.py b/plivo/resources/verify_apps.py new file mode 100644 index 00000000..9d140557 --- /dev/null +++ b/plivo/resources/verify_apps.py @@ -0,0 +1,258 @@ +# -*- coding: utf-8 -*- +from plivo.utils.validators import * + +from ..base import PlivoResource, PlivoResourceInterface, ResponseObject +from ..exceptions import * +from ..utils import * + + +class VerifyApp(PlivoResource): + _name = 'VerifyApp' + _identifier_string = 'app_uuid' + + def get(self): + return self.client.verify_apps.get(self.id) + + def update(self, + name=None, + brand_name=None, + otp_type=None, + otp_length=None, + otp_expiry=None, + otp_attempts=None, + max_validation_attempts=None, + sms_channel=None, + voice_channel=None, + wa_channel=None, + waba_id=None, + waba_phone_number=None, + waba_template_id=None, + template_uuid=None, + is_default=None, + message_redaction=None, + enable_fraudshield=None, + fs_protection_level=None, + customer_app_hash=None, + client=None): + return self.client.verify_apps.update( + self.id, + name=name, + brand_name=brand_name, + otp_type=otp_type, + otp_length=otp_length, + otp_expiry=otp_expiry, + otp_attempts=otp_attempts, + max_validation_attempts=max_validation_attempts, + sms_channel=sms_channel, + voice_channel=voice_channel, + wa_channel=wa_channel, + waba_id=waba_id, + waba_phone_number=waba_phone_number, + waba_template_id=waba_template_id, + template_uuid=template_uuid, + is_default=is_default, + message_redaction=message_redaction, + enable_fraudshield=enable_fraudshield, + fs_protection_level=fs_protection_level, + customer_app_hash=customer_app_hash, + client=client, + ) + + def delete(self): + return self.client.verify_apps.delete(self.id) + + +class ListVerifyAppsResponseObject(ResponseObject): + def __init__(self, client, dct): + super(ListVerifyAppsResponseObject, self).__init__(dct) + self.error = dct.get('error', None) + self.verify_apps = dct.get('verify_apps', None) + self.meta = dct.get('meta', None) + self.api_id = dct.get('api_id', None) + + def __iter__(self): + if self.verify_apps is not None: + return self.verify_apps.__iter__() + else: + return iter([]) + + def __len__(self): + if self.verify_apps is not None: + return len(self.verify_apps) + else: + return 0 + + def __str__(self): + import pprint + if self.verify_apps is not None: + response_dict = { + 'api_id': self.api_id, + 'meta': self.meta, + 'verify_apps': self.verify_apps, + } + return pprint.pformat(response_dict) + else: + return str(self.error) + + def __repr__(self): + if self.verify_apps is not None: + response_dict = { + 'api_id': self.api_id, + 'meta': self.meta, + 'verify_apps': self.verify_apps, + } + return str(response_dict) + else: + return str(self.error) + + def has_error(self): + return self.error is not None + + +class VerifyApps(PlivoResourceInterface): + _resource_type = VerifyApp + + @validate_args( + name=[of_type(six.text_type)], + brand_name=[optional(of_type(six.text_type))], + otp_type=[optional(of_type(six.text_type))], + otp_length=[optional(of_type(*six.integer_types))], + otp_expiry=[optional(of_type(*six.integer_types))], + otp_attempts=[optional(of_type(*six.integer_types))], + max_validation_attempts=[optional(of_type(*six.integer_types))], + sms_channel=[optional(of_type_exact(bool))], + voice_channel=[optional(of_type_exact(bool))], + wa_channel=[optional(of_type_exact(bool))], + waba_id=[optional(of_type(six.text_type))], + waba_phone_number=[optional(of_type(six.text_type))], + waba_template_id=[optional(of_type(six.text_type))], + template_uuid=[optional(of_type(six.text_type))], + is_default=[optional(of_type_exact(bool))], + message_redaction=[optional(of_type_exact(bool))], + enable_fraudshield=[optional(of_type_exact(bool))], + fs_protection_level=[optional(of_type(six.text_type))], + customer_app_hash=[optional(of_type(six.text_type))], + number_pool=[optional(of_type(six.text_type))], + ) + def create(self, + name, + brand_name=None, + otp_type=None, + otp_length=None, + otp_expiry=None, + otp_attempts=None, + max_validation_attempts=None, + sms_channel=None, + voice_channel=None, + wa_channel=None, + waba_id=None, + waba_phone_number=None, + waba_template_id=None, + template_uuid=None, + is_default=None, + message_redaction=None, + enable_fraudshield=None, + fs_protection_level=None, + customer_app_hash=None, + number_pool=None): + return self.client.request( + 'POST', ('Verify', 'App'), + to_param_dict(self.create, locals())) + + @validate_args( + name=[optional(of_type(six.text_type))], + subaccount=[optional(of_type(six.text_type))], + limit=[ + optional( + all_of( + of_type(*six.integer_types), + check(lambda limit: 0 < limit <= 20, '0 < limit <= 20'))) + ], + offset=[ + optional( + all_of( + of_type(*six.integer_types), + check(lambda offset: 0 <= offset, '0 <= offset'))) + ], + created_at=[optional(of_type(six.text_type))], + created_at__lt=[optional(of_type(six.text_type))], + created_at__lte=[optional(of_type(six.text_type))], + created_at__gt=[optional(of_type(six.text_type))], + created_at__gte=[optional(of_type(six.text_type))], + ) + def list(self, + name=None, + subaccount=None, + limit=None, + offset=None, + created_at=None, + created_at__lt=None, + created_at__lte=None, + created_at__gt=None, + created_at__gte=None): + return self.client.request( + 'GET', ('Verify', 'App'), + to_param_dict(self.list, locals()), + response_type=ListVerifyAppsResponseObject) + + def list_templates(self): + return self.client.request( + 'GET', ('Verify', 'App', 'templates'), response_type=None) + + @validate_args(app_uuid=[of_type(six.text_type)]) + def get(self, app_uuid): + return self.client.request( + 'GET', ('Verify', 'App', app_uuid), response_type=VerifyApp) + + @validate_args( + app_uuid=[of_type(six.text_type)], + name=[optional(of_type(six.text_type))], + brand_name=[optional(of_type(six.text_type))], + otp_type=[optional(of_type(six.text_type))], + otp_length=[optional(of_type(*six.integer_types))], + otp_expiry=[optional(of_type(*six.integer_types))], + otp_attempts=[optional(of_type(*six.integer_types))], + max_validation_attempts=[optional(of_type(*six.integer_types))], + sms_channel=[optional(of_type_exact(bool))], + voice_channel=[optional(of_type_exact(bool))], + wa_channel=[optional(of_type_exact(bool))], + waba_id=[optional(of_type(six.text_type))], + waba_phone_number=[optional(of_type(six.text_type))], + waba_template_id=[optional(of_type(six.text_type))], + template_uuid=[optional(of_type(six.text_type))], + is_default=[optional(of_type_exact(bool))], + message_redaction=[optional(of_type_exact(bool))], + enable_fraudshield=[optional(of_type_exact(bool))], + fs_protection_level=[optional(of_type(six.text_type))], + customer_app_hash=[optional(of_type(six.text_type))], + client=[optional(of_type(six.text_type))], + ) + def update(self, + app_uuid, + name=None, + brand_name=None, + otp_type=None, + otp_length=None, + otp_expiry=None, + otp_attempts=None, + max_validation_attempts=None, + sms_channel=None, + voice_channel=None, + wa_channel=None, + waba_id=None, + waba_phone_number=None, + waba_template_id=None, + template_uuid=None, + is_default=None, + message_redaction=None, + enable_fraudshield=None, + fs_protection_level=None, + customer_app_hash=None, + client=None): + return self.client.request( + 'POST', ('Verify', 'App', app_uuid), + to_param_dict(self.update, locals())) + + @validate_args(app_uuid=[of_type(six.text_type)]) + def delete(self, app_uuid): + return self.client.request('DELETE', ('Verify', 'App', app_uuid)) \ No newline at end of file diff --git a/plivo/resources/verify_session.py b/plivo/resources/verify_session.py new file mode 100644 index 00000000..cf088578 --- /dev/null +++ b/plivo/resources/verify_session.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +from plivo.utils.validators import * + +from ..base import PlivoResource, PlivoResourceInterface, ListSessionResponseObject +from ..exceptions import * +from ..utils import * + + +class VerifySession(PlivoResource): + _name = 'VerifySession' + _identifier_string = 'session_uuid' + + def get(self): + return self.client.verify_session.get(self.id) + + +class VerifySessions(PlivoResourceInterface): + _resource_type = VerifySession + + @validate_args( + app_hash=[optional(of_type(six.text_type))], + brand_name=[optional(of_type(six.text_type))], + code_length=[optional(of_type(*six.integer_types))], + dlt_entity_id=[optional(of_type(six.text_type))], + dlt_sender_id=[optional(of_type(six.text_type))], + dlt_template_category=[optional(of_type(six.text_type))], + dlt_template_id=[optional(of_type(six.text_type))], + dlt_text=[optional(of_type(six.text_type))], + dtmf=[optional(of_type(*six.integer_types))], + fraud_check=[optional(of_type(six.text_type))], + text=[optional(of_type(six.text_type))], + ) + def create(self, + app_hash=None, + brand_name=None, + code_length=None, + dlt_entity_id=None, + dlt_sender_id=None, + dlt_template_category=None, + dlt_template_id=None, + dlt_text=None, + dtmf=None, + fraud_check=None, + text=None): + return self.client.request( + 'POST', ('Verify', 'Session'), + to_param_dict(self.create, locals())) + + @validate_args(session_uuid=[of_type(six.text_type)]) + def get(self, session_uuid): + return self.client.request( + 'GET', ('Verify', 'Session', session_uuid), + response_type=VerifySession) + + @validate_args( + limit=[ + optional( + all_of( + of_type(*six.integer_types), + check(lambda limit: 0 < limit <= 20, '0 < limit <= 20'))) + ], + offset=[ + optional( + all_of( + of_type(*six.integer_types), + check(lambda offset: 0 <= offset, '0 <= offset'))) + ], + ) + def list(self, + limit=None, + offset=None): + return self.client.request( + 'GET', ('Verify', 'Session'), + to_param_dict(self.list, locals()), + response_type=ListSessionResponseObject, + objects_type=VerifySession) \ No newline at end of file diff --git a/plivo/resources/whatsapp_templates.py b/plivo/resources/whatsapp_templates.py new file mode 100644 index 00000000..2f622472 --- /dev/null +++ b/plivo/resources/whatsapp_templates.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +from plivo.utils.validators import * + +from ..base import PlivoResource, PlivoResourceInterface, ResponseObject +from ..exceptions import * +from ..utils import * + + +class WhatsappTemplate(PlivoResource): + _name = 'WhatsappTemplate' + _identifier_string = 'template_id' + + def get(self): + return self.client.whatsapp_templates.get(self.waba_id, self.id) + + def update(self, + name=None, + category=None, + language=None, + components=None, + allow_category_change=None): + return self.client.whatsapp_templates.update( + self.waba_id, + self.id, + name=name, + category=category, + language=language, + components=components, + allow_category_change=allow_category_change) + + def delete(self, name): + return self.client.whatsapp_templates.delete(self.waba_id, self.id, name) + + +class WhatsappTemplates(PlivoResourceInterface): + _resource_type = WhatsappTemplate + + @validate_args( + waba_id=[of_type(six.text_type)], + name=[optional(of_type(six.text_type))], + category=[optional(of_type(six.text_type))], + language=[optional(of_type(six.text_type))], + components=[optional(of_type_exact(list))], + allow_category_change=[optional(of_type_exact(bool))], + ) + def create(self, + waba_id, + name=None, + category=None, + language=None, + components=None, + allow_category_change=None): + return self.client.request( + 'POST', + ('WhatsApp', 'Template', waba_id), + to_param_dict(self.create, locals())) + + @validate_args( + waba_id=[of_type(six.text_type)], + template_id=[of_type(six.text_type)], + name=[optional(of_type(six.text_type))], + category=[optional(of_type(six.text_type))], + language=[optional(of_type(six.text_type))], + components=[optional(of_type_exact(list))], + allow_category_change=[optional(of_type_exact(bool))], + ) + def update(self, + waba_id, + template_id, + name=None, + category=None, + language=None, + components=None, + allow_category_change=None): + return self.client.request( + 'POST', + ('WhatsApp', 'Template', waba_id, template_id), + to_param_dict(self.update, locals())) + + @validate_args( + waba_id=[of_type(six.text_type)], + template_id=[of_type(six.text_type)], + ) + def get(self, waba_id, template_id): + return self.client.request( + 'GET', + ('WhatsApp', 'Template', waba_id, template_id), + response_type=WhatsappTemplate) + + @validate_args( + waba_id=[of_type(six.text_type)], + template_name=[optional(of_type(six.text_type))], + limit=[ + optional( + all_of( + of_type(*six.integer_types), + check(lambda limit: 0 < limit <= 20, '0 < limit <= 20'))) + ], + offset=[ + optional( + all_of( + of_type(*six.integer_types), + check(lambda offset: 0 <= offset, '0 <= offset'))) + ], + ) + def list(self, + waba_id, + template_name=None, + limit=None, + offset=None): + return self.client.request( + 'GET', + ('WhatsApp', 'Template', waba_id), + to_param_dict(self.list, locals())) + + @validate_args( + waba_id=[of_type(six.text_type)], + template_id=[of_type(six.text_type)], + name=[of_type(six.text_type)], + ) + def delete(self, waba_id, template_id, name): + return self.client.request( + 'DELETE', + ('WhatsApp', 'Template', waba_id, template_id), + to_param_dict(self.delete, locals())) \ No newline at end of file diff --git a/setup.py b/setup.py index 5aa4ad1e..18f23075 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( name='plivo', - version='4.60.1', + version='4.61.0', description='A Python SDK to make voice calls & send SMS using Plivo and to generate Plivo XML', long_description=long_description, url='https://github.com/plivo/plivo-python', diff --git a/tests/resources/test_messages.py b/tests/resources/test_messages.py index e872db06..7cef9d16 100644 --- a/tests/resources/test_messages.py +++ b/tests/resources/test_messages.py @@ -59,6 +59,26 @@ def test_send_message_with_no_src_powerpack(self): dst='1234', text='Abcd') + def test_send_message_with_content_message(self): + expected_response = {'message_uuid': 'adsdafkjadshf123123'} + self.client.set_expected_response( + status_code=202, data_to_return=expected_response) + + content_message = { + 'type': 'text', + 'text': {'text': 'Hello via RCS'}, + } + test_message = self.client.messages.create( + src='1234', dst='12345', type_='rcs', + content_message=content_message) + + self.assertEqual( + self.client.current_request.url, + 'https://api.plivo.com/v1/Account/MAXXXXXXXXXXXXXXXXXX/Message/') + self.assertEqual(self.client.current_request.method, 'POST') + self.assertEqual(test_message.message_uuid, + expected_response['message_uuid']) + @with_response(200) def test_get(self): message_uuid = 'message_uuid' @@ -91,4 +111,4 @@ def test_list(self): self.assertEqual(len(list(messages)), 20) self.assertUrlEqual(self.client.current_request.url, self.get_url('Message')) - self.assertEqual(self.client.current_request.method, 'GET') + self.assertEqual(self.client.current_request.method, 'GET') \ No newline at end of file diff --git a/tests/resources/test_rcs.py b/tests/resources/test_rcs.py new file mode 100644 index 00000000..561544f1 --- /dev/null +++ b/tests/resources/test_rcs.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +from tests.base import PlivoResourceTestCase + + +class RcsCapabilityTest(PlivoResourceTestCase): + def test_check_rcs_capability(self): + expected_response = { + 'api_id': 'some-api-id', + 'phone_number': '+14151234567', + 'is_capable': True, + 'features': ['CHAT', 'FILE_TRANSFER'], + 'message': 'Number is RCS capable.', + 'error': None, + } + self.client.set_expected_response( + status_code=200, data_to_return=expected_response) + + response = self.client.rcs_capability.check( + phone_number='+14151234567') + + self.assertUrlEqual( + self.client.current_request.url, + self.get_url('RCS', 'Capability'), + ) + self.assertEqual(self.client.current_request.method, 'GET') + self.assertEqual(response.phone_number, expected_response['phone_number']) + self.assertEqual(response.is_capable, expected_response['is_capable']) + self.assertEqual(response.features, expected_response['features']) + + def test_check_rcs_capability_with_agent_uuid(self): + expected_response = { + 'api_id': 'some-api-id', + 'phone_number': '+14151234567', + 'is_capable': False, + 'features': [], + 'message': 'Number is not RCS capable.', + 'error': None, + } + self.client.set_expected_response( + status_code=200, data_to_return=expected_response) + + response = self.client.rcs_capability.check( + phone_number='+14151234567', + agent_uuid='some-agent-uuid') + + self.assertUrlEqual( + self.client.current_request.url, + self.get_url('RCS', 'Capability'), + ) + self.assertEqual(self.client.current_request.method, 'GET') + self.assertEqual(response.is_capable, expected_response['is_capable']) + + def test_check_rcs_capability_missing_phone_number(self): + from plivo.exceptions import ValidationError + self.assertRaises( + (ValidationError, TypeError), + self.client.rcs_capability.check, + ) \ No newline at end of file diff --git a/tests/resources/test_rcs_assistant_events.py b/tests/resources/test_rcs_assistant_events.py new file mode 100644 index 00000000..b491d26e --- /dev/null +++ b/tests/resources/test_rcs_assistant_events.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +from tests.base import PlivoResourceTestCase + + +class RcsAssistantEventsTest(PlivoResourceTestCase): + def test_create(self): + expected_response = { + 'api_id': 'test-api-id', + 'phone_number': '+14155551234', + 'is_capable': True, + 'features': ['RICHCARD_STANDALONE', 'ACTION_CREATE_CALENDAR_EVENT'], + 'message': 'RCS assistant event sent successfully.', + 'error': None, + } + self.client.set_expected_response( + status_code=202, data_to_return=expected_response) + + response = self.client.rcs_assistant_events.create() + + self.assertEqual(self.client.current_request.method, 'POST') + self.assertUrlEqual( + self.client.current_request.url, + self.get_url('RCS', 'AssistantEvents'), + ) + self.assertEqual(response.api_id, expected_response['api_id']) + self.assertEqual(response.phone_number, expected_response['phone_number']) + self.assertEqual(response.is_capable, expected_response['is_capable']) + self.assertEqual(response.features, expected_response['features']) + self.assertEqual(response.message, expected_response['message']) + self.assertEqual(response.error, expected_response['error']) \ No newline at end of file diff --git a/tests/resources/test_verify_apps.py b/tests/resources/test_verify_apps.py new file mode 100644 index 00000000..d0975b44 --- /dev/null +++ b/tests/resources/test_verify_apps.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +from tests.base import PlivoResourceTestCase + + +class VerifyAppTest(PlivoResourceTestCase): + def test_create_verify_app(self): + expected_response = { + 'api_id': 'some-api-id', + 'app_uuid': 'some-app-uuid', + 'message': 'Verify app created', + } + self.client.set_expected_response( + status_code=201, data_to_return=expected_response) + + response = self.client.verify_apps.create(name='TestApp') + + self.assertUrlEqual( + self.client.current_request.url, + self.get_url('Verify', 'App')) + self.assertEqual(self.client.current_request.method, 'POST') + self.assertEqual(response.app_uuid, expected_response['app_uuid']) + + def test_create_verify_app_with_all_params(self): + expected_response = { + 'api_id': 'some-api-id', + 'app_uuid': 'some-app-uuid', + 'message': 'Verify app created', + } + self.client.set_expected_response( + status_code=201, data_to_return=expected_response) + + response = self.client.verify_apps.create( + name='TestApp', + brand_name='MyBrand', + otp_type='numeric', + otp_length=6, + otp_expiry=300, + otp_attempts=3, + max_validation_attempts=5, + sms_channel=True, + voice_channel=False, + wa_channel=False, + is_default=False, + message_redaction=False, + enable_fraudshield=False, + number_pool='pool-id', + ) + + self.assertEqual(self.client.current_request.method, 'POST') + self.assertEqual(response.app_uuid, expected_response['app_uuid']) + + def test_list_verify_apps(self): + expected_response = { + 'api_id': 'some-api-id', + 'verify_apps': [ + {'app_uuid': 'uuid-1', 'name': 'App1'}, + {'app_uuid': 'uuid-2', 'name': 'App2'}, + ], + 'meta': {'limit': 20, 'offset': 0, 'total_count': 2}, + } + self.client.set_expected_response( + status_code=200, data_to_return=expected_response) + + response = self.client.verify_apps.list() + + self.assertUrlEqual( + self.client.current_request.url, + self.get_url('Verify', 'App')) + self.assertEqual(self.client.current_request.method, 'GET') + self.assertEqual(len(list(response)), 2) + + def test_list_verify_apps_with_filters(self): + expected_response = { + 'api_id': 'some-api-id', + 'verify_apps': [ + {'app_uuid': 'uuid-1', 'name': 'App1'}, + ], + 'meta': {'limit': 20, 'offset': 0, 'total_count': 1}, + } + self.client.set_expected_response( + status_code=200, data_to_return=expected_response) + + response = self.client.verify_apps.list( + name='App1', + limit=20, + offset=0, + ) + + self.assertEqual(self.client.current_request.method, 'GET') + self.assertEqual(len(list(response)), 1) + + def test_list_templates(self): + expected_response = { + 'api_id': 'some-api-id', + 'templates': [ + {'template_uuid': 'tmpl-1', 'name': 'Default OTP'}, + ], + } + self.client.set_expected_response( + status_code=200, data_to_return=expected_response) + + response = self.client.verify_apps.list_templates() + + self.assertUrlEqual( + self.client.current_request.url, + self.get_url('Verify', 'App', 'templates')) + self.assertEqual(self.client.current_request.method, 'GET') + + def test_get_verify_app(self): + app_uuid = 'some-app-uuid' + expected_response = { + 'api_id': 'some-api-id', + 'verify_app': {'app_uuid': app_uuid, 'name': 'TestApp'}, + 'verify_whatsapp': None, + } + self.client.set_expected_response( + status_code=200, data_to_return=expected_response) + + response = self.client.verify_apps.get(app_uuid) + + self.assertUrlEqual( + self.client.current_request.url, + self.get_url('Verify', 'App', app_uuid)) + self.assertEqual(self.client.current_request.method, 'GET') + + def test_update_verify_app(self): + app_uuid = 'some-app-uuid' + expected_response = { + 'api_id': 'some-api-id', + 'app_uuid': app_uuid, + 'message': 'Verify app updated', + } + self.client.set_expected_response( + status_code=200, data_to_return=expected_response) + + response = self.client.verify_apps.update( + app_uuid, name='UpdatedName', otp_length=4) + + self.assertUrlEqual( + self.client.current_request.url, + self.get_url('Verify', 'App', app_uuid)) + self.assertEqual(self.client.current_request.method, 'POST') + self.assertEqual(response.app_uuid, app_uuid) + + def test_delete_verify_app(self): + app_uuid = 'some-app-uuid' + self.client.set_expected_response(status_code=204, data_to_return={}) + + self.client.verify_apps.delete(app_uuid) + + self.assertUrlEqual( + self.client.current_request.url, + self.get_url('Verify', 'App', app_uuid)) + self.assertEqual(self.client.current_request.method, 'DELETE') \ No newline at end of file diff --git a/tests/resources/test_verify_session.py b/tests/resources/test_verify_session.py new file mode 100644 index 00000000..779f5c32 --- /dev/null +++ b/tests/resources/test_verify_session.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +from tests.base import PlivoResourceTestCase +from tests.decorators import with_response + + +class VerifySessionTest(PlivoResourceTestCase): + + def test_create_session_minimal(self): + expected_response = {'session_uuid': 'abc123sessionuuid', 'api_id': 'some-api-id'} + self.client.set_expected_response( + status_code=201, data_to_return=expected_response) + + response = self.client.verify_session.create() + + self.assertUrlEqual( + self.client.current_request.url, + self.get_url('Verify', 'Session')) + self.assertEqual(self.client.current_request.method, 'POST') + + def test_create_session_with_all_params(self): + expected_response = {'session_uuid': 'abc123sessionuuid', 'api_id': 'some-api-id'} + self.client.set_expected_response( + status_code=201, data_to_return=expected_response) + + response = self.client.verify_session.create( + app_hash='app_hash_value', + brand_name='MyBrand', + code_length=6, + dlt_entity_id='entity123', + dlt_sender_id='sender123', + dlt_template_category='transactional', + dlt_template_id='template123', + dlt_text='Your OTP is {otp}', + dtmf=1, + fraud_check='medium', + text='Your OTP is {otp}. Valid for 10 minutes.', + ) + + self.assertUrlEqual( + self.client.current_request.url, + self.get_url('Verify', 'Session')) + self.assertEqual(self.client.current_request.method, 'POST') + + def test_get_session(self): + expected_response = {'session_uuid': 'abc123sessionuuid', 'api_id': 'some-api-id', 'status': 'otp_sent'} + self.client.set_expected_response( + status_code=200, data_to_return=expected_response) + + session_uuid = 'abc123sessionuuid' + response = self.client.verify_session.get(session_uuid) + + self.assertUrlEqual( + self.client.current_request.url, + self.get_url('Verify', 'Session', session_uuid)) + self.assertEqual(self.client.current_request.method, 'GET') + + def test_list_sessions(self): + expected_response = { + 'api_id': 'some-api-id', + 'sessions': [], + 'meta': {'limit': 20, 'offset': 0, 'total_count': 0, 'next': None, 'previous': None}, + } + self.client.set_expected_response( + status_code=200, data_to_return=expected_response) + + response = self.client.verify_session.list() + + self.assertUrlEqual( + self.client.current_request.url, + self.get_url('Verify', 'Session')) + self.assertEqual(self.client.current_request.method, 'GET') \ No newline at end of file diff --git a/tests/resources/test_whatsapp_templates.py b/tests/resources/test_whatsapp_templates.py new file mode 100644 index 00000000..081059d7 --- /dev/null +++ b/tests/resources/test_whatsapp_templates.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +from tests.base import PlivoResourceTestCase + + +class WhatsappTemplateTest(PlivoResourceTestCase): + + def test_create_template(self): + expected_response = { + 'api_id': 'api-id-xxx', + 'template_id': 'tmpl-001', + 'template_name': 'my_template', + 'template_status': 'PENDING', + 'template_category': 'MARKETING', + 'template_language': 'en_US', + 'status': 'success', + 'message': 'Template created', + } + self.client.set_expected_response( + status_code=201, data_to_return=expected_response) + + response = self.client.whatsapp_templates.create( + waba_id='waba-001', + name='my_template', + category='MARKETING', + language='en_US', + components=[{'type': 'BODY', 'text': 'Hello'}], + allow_category_change=True, + ) + + self.assertUrlEqual( + self.client.current_request.url, + self.get_url('WhatsApp', 'Template', 'waba-001')) + self.assertEqual(self.client.current_request.method, 'POST') + + def test_update_template(self): + expected_response = { + 'api_id': 'api-id-xxx', + 'template_id': 'tmpl-001', + 'template_name': 'my_template', + 'template_status': 'PENDING', + 'template_category': 'UTILITY', + 'template_language': 'en_US', + 'status': 'success', + 'message': 'Template updated', + } + self.client.set_expected_response( + status_code=200, data_to_return=expected_response) + + response = self.client.whatsapp_templates.update( + waba_id='waba-001', + template_id='tmpl-001', + category='UTILITY', + ) + + self.assertUrlEqual( + self.client.current_request.url, + self.get_url('WhatsApp', 'Template', 'waba-001', 'tmpl-001')) + self.assertEqual(self.client.current_request.method, 'POST') + + def test_get_template(self): + expected_response = { + 'api_id': 'api-id-xxx', + 'template_id': 'tmpl-001', + 'name': 'my_template', + 'category': 'MARKETING', + 'language': 'en_US', + 'status': 'APPROVED', + 'components': [{'type': 'BODY', 'text': 'Hello'}], + 'quality_score': {'score': 'GREEN'}, + 'rejected_reason': None, + 'message': None, + 'error': None, + } + self.client.set_expected_response( + status_code=200, data_to_return=expected_response) + + response = self.client.whatsapp_templates.get( + waba_id='waba-001', + template_id='tmpl-001', + ) + + self.assertUrlEqual( + self.client.current_request.url, + self.get_url('WhatsApp', 'Template', 'waba-001', 'tmpl-001')) + self.assertEqual(self.client.current_request.method, 'GET') + + def test_list_templates(self): + expected_response = { + 'api_id': 'api-id-xxx', + 'objects': [ + {'template_id': 'tmpl-001', 'name': 'my_template'}, + {'template_id': 'tmpl-002', 'name': 'other_template'}, + ], + 'meta': {'total_count': 2, 'limit': 20, 'offset': 0}, + 'status': 'success', + 'message': None, + 'error': None, + } + self.client.set_expected_response( + status_code=200, data_to_return=expected_response) + + response = self.client.whatsapp_templates.list( + waba_id='waba-001', + template_name='my_template', + limit=20, + offset=0, + ) + + self.assertUrlEqual( + self.client.current_request.url, + self.get_url('WhatsApp', 'Template', 'waba-001')) + self.assertEqual(self.client.current_request.method, 'GET') + + def test_delete_template(self): + self.client.set_expected_response(status_code=204, data_to_return={}) + + self.client.whatsapp_templates.delete( + waba_id='waba-001', + template_id='tmpl-001', + name='my_template', + ) + + self.assertUrlEqual( + self.client.current_request.url, + self.get_url('WhatsApp', 'Template', 'waba-001', 'tmpl-001')) + self.assertEqual(self.client.current_request.method, 'DELETE') \ No newline at end of file