diff --git a/.travis.yml b/.travis.yml index 5c9e0e9..1beabcd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,10 +11,13 @@ install: - pip install requests - pip install codecov - pip install pytest pytest-cov + - pip install pycodestyle - pip install . script: - coverage run --source=messagebird -m unittest discover -s tests/ - coverage report --fail-under=80 + - pycodestyle --statistics --ignore=E121,E123,E126,E133,E226,E241,E242,E704,W503,W504,W505,E501 ./messagebird/ + - pycodestyle --statistics --ignore=E121,E123,E126,E133,E226,E241,E242,E704,W503,W504,W505,E501 ./tests/ matrix: allow_failures: - python: 'nightly' diff --git a/README.md b/README.md index 2c76ec2..a2c82e4 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,11 @@ Your balance: Please see the other examples for a complete overview of all the available API calls. +To run examples with arguments, try: +```shell script +$ python ./examples/voice_create_webhook.py --accessKey accessKeyWhichNotExist --url https://example.com --title HELLO_WEBHOOK --token HELLO_TOKEN +``` + Conversations WhatsApp Sandbox ------------- To use the whatsapp sandbox you need to add `messagebird.Feature.ENABLE_CONVERSATIONS_API_WHATSAPP_SANDBOX` to the list of features you want enabled. Don't forget to replace `YOUR_ACCESS_KEY` with your actual access key. diff --git a/examples/voice_create_webhook.py b/examples/voice_create_webhook.py new file mode 100644 index 0000000..a8785ee --- /dev/null +++ b/examples/voice_create_webhook.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +import argparse +import messagebird +from messagebird.voice_webhook import VoiceCreateWebhookRequest + +parser = argparse.ArgumentParser() +parser.add_argument('--accessKey', help='access key for MessageBird API', type=str, required=True) +parser.add_argument('--url', help='url for the webhook', type=str, required=True) +parser.add_argument('--title', help='title for the webhook', type=str) +parser.add_argument('--token', help='token for the webhook', type=str) +args = vars(parser.parse_args()) + +try: + client = messagebird.Client(args['accessKey']) + + create_webhook_request = VoiceCreateWebhookRequest(url=args['url'], title=args['title'], token=args['token']) + webhook = client.voice_create_webhook(create_webhook_request) + + # Print the object information. + print('\nThe following information was returned as a Voice Webhook object:\n') + print(' id : {}'.format(webhook.id)) + print(' token : {}'.format(webhook.token)) + print(' url : {}'.format(webhook.url)) + print(' createdAt : {}'.format(webhook.createdAt)) + print(' updatedAt : {}'.format(webhook.updatedAt)) + +except messagebird.client.ErrorException as e: + print('An error occured while creating a Voice Webhook object:') + + for error in e.errors: + print(' code : {}'.format(error.code)) + print(' description : {}'.format(error.description)) + print(' parameter : {}\n'.format(error.parameter)) + + + diff --git a/examples/voice_delete_webhook.py b/examples/voice_delete_webhook.py new file mode 100644 index 0000000..dd09516 --- /dev/null +++ b/examples/voice_delete_webhook.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +import argparse +import messagebird + +parser = argparse.ArgumentParser() +parser.add_argument('--accessKey', help='access key for MessageBird API', type=str, required=True) +parser.add_argument('--webhookId', help='webhook that you want to update', type=str, required=True) + +args = vars(parser.parse_args()) + +try: + client = messagebird.Client(args['accessKey']) + webhook = client.voice_delete_webhook(args['webhookId']) + + # Print the object information. + print('Webhook has been deleted') + +except messagebird.client.ErrorException as e: + print('An error occured while deleting a Voice Webhook object:') + + for error in e.errors: + print(' code : {}'.format(error.code)) + print(' description : {}'.format(error.description)) + print(' parameter : {}\n'.format(error.parameter)) diff --git a/examples/voice_list_webhook.py b/examples/voice_list_webhook.py new file mode 100644 index 0000000..4df4b2f --- /dev/null +++ b/examples/voice_list_webhook.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +import argparse +import messagebird +from messagebird.voice_webhook import VoiceCreateWebhookRequest + +parser = argparse.ArgumentParser() +parser.add_argument('--accessKey', help='access key for MessageBird API', type=str, required=True) + +args = vars(parser.parse_args()) + +try: + client = messagebird.Client(args['accessKey']) + + webhooks_list = client.voice_list_webhooks(limit=5, offset=0) + + if webhooks_list is None or webhooks_list.data is None: + print("\nNo webhooks\n") + exit(0) + + # Print the object information. + print('\nThe following information was returned as a Voice Webhook objects:\n') + for webhook in webhooks_list.data: + print('{') + print(' id : {}'.format(webhook.id)) + print(' token : {}'.format(webhook.token)) + print(' url : {}'.format(webhook.url)) + print(' createdAt : {}'.format(webhook.createdAt)) + print(' updatedAt : {}'.format(webhook.updatedAt)) + print('}\n') + +except messagebird.client.ErrorException as e: + print('An error occured while reading a Voice Webhook object:') + + for error in e.errors: + print(' code : {}'.format(error.code)) + print(' description : {}'.format(error.description)) + print(' parameter : {}\n'.format(error.parameter)) diff --git a/examples/voice_read_webhook.py b/examples/voice_read_webhook.py new file mode 100644 index 0000000..b5b08de --- /dev/null +++ b/examples/voice_read_webhook.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +import argparse +import messagebird +from messagebird.voice_webhook import VoiceCreateWebhookRequest + +parser = argparse.ArgumentParser() +parser.add_argument('--accessKey', help='access key for MessageBird API', type=str, required=True) +parser.add_argument('--webhookId', help='webhook that you want to update', type=str, required=True) + +args = vars(parser.parse_args()) + +try: + client = messagebird.Client(args['accessKey']) + + webhook = client.voice_read_webhook(args['webhookId']) + + # Print the object information. + print('\nThe following information was returned as a Voice Webhook object:\n') + + print(' id : {}'.format(webhook.id)) + print(' token : {}'.format(webhook.token)) + print(' url : {}'.format(webhook.url)) + print(' createdAt : {}'.format(webhook.createdAt)) + print(' updatedAt : {}'.format(webhook.updatedAt)) + + +except messagebird.client.ErrorException as e: + print('An error occured while reading a Voice Webhook object:') + + for error in e.errors: + print(' code : {}'.format(error.code)) + print(' description : {}'.format(error.description)) + print(' parameter : {}\n'.format(error.parameter)) diff --git a/examples/voice_update_webhook.py b/examples/voice_update_webhook.py new file mode 100644 index 0000000..7982ef9 --- /dev/null +++ b/examples/voice_update_webhook.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +import argparse +import messagebird +from messagebird.voice_webhook import VoiceUpdateWebhookRequest + +parser = argparse.ArgumentParser() +parser.add_argument('--accessKey', help='access key for MessageBird API', type=str, required=True) +parser.add_argument('--webhookId', help='webhook that you want to update', type=str, required=True) +parser.add_argument('--title', help='title for the webhook', type=str) +parser.add_argument('--token', help='token for the webhook', type=str) + +args = vars(parser.parse_args()) + +try: + client = messagebird.Client(args['accessKey']) + + update_webhook_request = VoiceUpdateWebhookRequest(title=args['title'], token=args['token']) + webhook = client.voice_update_webhook(args['webhookId'], update_webhook_request) + + # Print the object information. + print('\nThe following information was returned as a Voice Webhook object:\n') + print(' id : {}'.format(webhook.id)) + print(' token : {}'.format(webhook.token)) + print(' url : {}'.format(webhook.url)) + print(' createdAt : {}'.format(webhook.createdAt)) + print(' updatedAt : {}'.format(webhook.updatedAt)) + +except messagebird.client.ErrorException as e: + print('An error occured while updating a Voice Webhook object:') + + for error in e.errors: + print(' code : {}'.format(error.code)) + print(' description : {}'.format(error.description)) + print(' parameter : {}\n'.format(error.parameter)) + + + diff --git a/messagebird/balance.py b/messagebird/balance.py index 33e9086..8ba8860 100644 --- a/messagebird/balance.py +++ b/messagebird/balance.py @@ -1,7 +1,8 @@ from messagebird.base import Base + class Balance(Base): - def __init__(self): - self.amount = None - self.type = None - self.payment = None + def __init__(self): + self.amount = None + self.type = None + self.payment = None diff --git a/messagebird/base.py b/messagebird/base.py index e64707e..e42a393 100644 --- a/messagebird/base.py +++ b/messagebird/base.py @@ -1,17 +1,18 @@ from datetime import datetime import dateutil.parser +import json class Base(object): - def load(self, data): - for name, value in list(data.items()): - if hasattr(self, name) and not callable(getattr(self,name)): - setattr(self, name, value) + def load(self, data): + for name, value in list(data.items()): + if hasattr(self, name) and not callable(getattr(self, name)): + setattr(self, name, value) - return self + return self - @staticmethod - def value_to_time(value, format='%Y-%m-%dT%H:%M:%S+00:00'): - if value is not None: - return dateutil.parser.parse(value).replace(microsecond=0) + @staticmethod + def value_to_time(value, format='%Y-%m-%dT%H:%M:%S+00:00'): + if value is not None: + return dateutil.parser.parse(value).replace(microsecond=0) diff --git a/messagebird/call.py b/messagebird/call.py index 4575d9d..e8d2a10 100644 --- a/messagebird/call.py +++ b/messagebird/call.py @@ -15,7 +15,7 @@ def __init__(self): @property def data(self): return self._data - + @data.setter def data(self, value): self._data = CallData().load(value[0]) @@ -23,5 +23,5 @@ def data(self, value): def __str__(self): return "\n".join([ 'id : %s' % self.id, - 'data.'+'data.'.join(str(self._data).splitlines(True)), - ]) \ No newline at end of file + 'data.' + 'data.'.join(str(self._data).splitlines(True)), + ]) diff --git a/messagebird/call_data.py b/messagebird/call_data.py index 2cc730d..1287e89 100644 --- a/messagebird/call_data.py +++ b/messagebird/call_data.py @@ -1,8 +1,9 @@ from messagebird.base import Base from messagebird.webhook import Webhook + class CallData(Base): - + def __init__(self): self.id = None self.status = None @@ -13,11 +14,10 @@ def __init__(self): self._endedAt = None self._webhook = None - @property def updatedAt(self): return self._updatedAt - + @updatedAt.setter def updatedAt(self, value): self._updatedAt = self.value_to_time(value, '%Y-%m-%dT%H:%M:%SZ') @@ -25,7 +25,7 @@ def updatedAt(self, value): @property def createdAt(self): return self._createdAt - + @createdAt.setter def createdAt(self, value): self._createdAt = self.value_to_time(value, '%Y-%m-%dT%H:%M:%SZ') @@ -33,7 +33,7 @@ def createdAt(self, value): @property def endedAt(self): return self._endedAt - + @endedAt.setter def endedAt(self, value): self._endedAt = self.value_to_time(value, '%Y-%m-%dT%H:%M:%SZ') @@ -56,4 +56,4 @@ def __str__(self): 'updatedAt : %s' % self.updatedAt, 'createdAt : %s' % self.createdAt, 'endedAt : %s' % self.endedAt, - ]) \ No newline at end of file + ]) diff --git a/messagebird/call_flow.py b/messagebird/call_flow.py index 8e61d08..2211f9d 100644 --- a/messagebird/call_flow.py +++ b/messagebird/call_flow.py @@ -60,6 +60,7 @@ def data(self, value): self._data = items + class CallFlow(Base): def __init__(self): @@ -134,4 +135,4 @@ def load(self, data): if hasattr(self, name) and not callable(getattr(self, name)): setattr(self, name, value) - return self \ No newline at end of file + return self diff --git a/messagebird/call_list.py b/messagebird/call_list.py index 0d2ec4f..e33f263 100644 --- a/messagebird/call_list.py +++ b/messagebird/call_list.py @@ -39,4 +39,3 @@ def data(self, value): if isinstance(value, list): self.count = len(value) self.items = value - diff --git a/messagebird/client.py b/messagebird/client.py index 45a5464..dbcf778 100644 --- a/messagebird/client.py +++ b/messagebird/client.py @@ -7,11 +7,12 @@ from messagebird.call import Call from messagebird.call_list import CallList from messagebird.contact import Contact, ContactList -from messagebird.error import Error +from messagebird.error import Error, ValidationError from messagebird.group import Group, GroupList from messagebird.hlr import HLR from messagebird.message import Message, MessageList from messagebird.mms import MMS +from messagebird.voice_webhook import VoiceWebhook, VoiceWebhookList from messagebird.voicemessage import VoiceMessage from messagebird.lookup import Lookup from messagebird.verify import Verify @@ -23,7 +24,6 @@ from messagebird.voice_transcription import VoiceTranscriptionsList, VoiceTranscriptionsView from messagebird.call_flow import CallFlow, CallFlowList, CallFlowNumberList - ENDPOINT = 'https://rest.messagebird.com' CLIENT_VERSION = '1.4.1' PYTHON_VERSION = '%d.%d.%d' % (sys.version_info[0], sys.version_info[1], sys.version_info[2]) @@ -33,8 +33,6 @@ CONVERSATION_API_ROOT = 'https://conversations.messagebird.com/v1/' CONVERSATION_API_WHATSAPP_SANDBOX_ROOT = 'https://whatsapp-sandbox.messagebird.com/v1/' - - CONVERSATION_PATH = 'conversations' CONVERSATION_MESSAGES_PATH = 'messages' CONVERSATION_WEB_HOOKS_PATH = 'webhooks' @@ -46,6 +44,7 @@ VOICE_LEGS_PATH = 'legs' VOICE_RECORDINGS_PATH = 'recordings' VOICE_TRANSCRIPTIONS_PATH = 'transcriptions' +VOICE_WEB_HOOKS_PATH = 'webhooks' class ErrorException(Exception): @@ -54,12 +53,15 @@ def __init__(self, errors): message = ' '.join([str(e) for e in self.errors]) super(ErrorException, self).__init__(message) + class SignleErrorException(Exception): def __init__(self, errorMessage): super(SignleErrorException, self).__init__(errorMessage) + class Feature(enum.Enum): - ENABLE_CONVERSATIONS_API_WHATSAPP_SANDBOX = 1 + ENABLE_CONVERSATIONS_API_WHATSAPP_SANDBOX = 1 + class Client(object): @@ -128,7 +130,7 @@ def balance(self): """Retrieve your balance.""" return Balance().load(self.request('balance')) - def call(self,id): + def call(self, id): """Retrieve the information of a specific call""" return Call().load(self.request('calls/' + str(id), 'GET', None, VOICE_TYPE)) @@ -159,7 +161,7 @@ def call_create(self, source, destination, callFlow, webhook): Call(object) : The Call object just created.""" params = locals() - del(params['self']) + del (params['self']) return Call().load(self.request('calls', 'POST', params, VOICE_TYPE)) def call_delete(self, id): @@ -184,12 +186,13 @@ def message(self, id): def message_list(self, limit=20, offset=0): """Retrieve a list of the most recent messages.""" - query = 'limit=' + str(limit) + '&offset=' + str(offset) + query = self._format_query(limit, offset) return MessageList().load(self.request('messages?' + query)) def message_create(self, originator, recipients, body, params=None): """Create a new message.""" - if params is None: params = {} + if params is None: + params = {} if type(recipients) == list: recipients = ','.join(recipients) @@ -200,7 +203,7 @@ def message_delete(self, id): """Delete a message from the dashboard.""" self.request_plain_text('messages/' + str(id), 'DELETE') - def mms_create(self, originator, recipients, body, mediaUrls, subject = None, reference = None, scheduledDatetime = None): + def mms_create(self, originator, recipients, body, mediaUrls, subject=None, reference=None, scheduledDatetime=None): """ Send bulk mms. Args: @@ -217,13 +220,13 @@ def mms_create(self, originator, recipients, body, mediaUrls, subject = None, re Returns: MMS: On success an MMS instance instantiated with success response """ - if isinstance(recipients,list): + if isinstance(recipients, list): recipients = ','.join(recipients) - if isinstance(mediaUrls,str): + if isinstance(mediaUrls, str): mediaUrls = [mediaUrls] params = locals() - del(params['self']) - return MMS().load(self.request('mms', 'POST', params)) + del (params['self']) + return MMS().load(self.request('mms', 'POST', params)) def voice_message(self, id): "Retrieve the information of a specific voice message." @@ -231,7 +234,8 @@ def voice_message(self, id): def voice_message_create(self, recipients, body, params=None): """Create a new voice message.""" - if params is None: params = {} + if params is None: + params = {} if type(recipients) == list: recipients = ','.join(recipients) @@ -240,17 +244,20 @@ def voice_message_create(self, recipients, body, params=None): def lookup(self, phonenumber, params=None): """Do a new lookup.""" - if params is None: params = {} + if params is None: + params = {} return Lookup().load(self.request('lookup/' + str(phonenumber), 'GET', params)) def lookup_hlr(self, phonenumber, params=None): """Retrieve the information of a specific HLR lookup.""" - if params is None: params = {} + if params is None: + params = {} return HLR().load(self.request('lookup/' + str(phonenumber) + '/hlr', 'GET', params)) def lookup_hlr_create(self, phonenumber, params=None): """Perform a new HLR lookup.""" - if params is None: params = {} + if params is None: + params = {} return HLR().load(self.request('lookup/' + str(phonenumber) + '/hlr', 'POST', params)) def verify(self, id): @@ -259,7 +266,8 @@ def verify(self, id): def verify_create(self, recipient, params=None): """Create a new verification.""" - if params is None: params = {} + if params is None: + params = {} params.update({'recipient': recipient}) return Verify().load(self.request('verify', 'POST', params)) @@ -276,7 +284,8 @@ def contact(self, id): return Contact().load(self.request('contacts/' + str(id))) def contact_create(self, phonenumber, params=None): - if params is None: params = {} + if params is None: + params = {} params.update({'msisdn': phonenumber}) return Contact().load(self.request('contacts', 'POST', params)) @@ -294,7 +303,8 @@ def group(self, id): return Group().load(self.request('groups/' + str(id), 'GET', None)) def group_create(self, name, params=None): - if params is None: params = {} + if params is None: + params = {} params.update({'name': name}) return Group().load(self.request('groups', 'POST', params)) @@ -306,7 +316,8 @@ def group_list(self, limit=10, offset=0): return GroupList().load(self.request('groups?' + query, 'GET', None)) def group_update(self, id, name, params=None): - if params is None: params = {} + if params is None: + params = {} params.update({'name': name}) self.request_plain_text('groups/' + str(id), 'PATCH', params) @@ -381,32 +392,32 @@ def conversation_read_webhook(self, id): return ConversationWebhook().load(self.request(uri, 'GET', None, CONVERSATION_TYPE)) def voice_recording_list_recordings(self, call_id, leg_id): - uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' + str(call_id) + '/' + VOICE_LEGS_PATH + '/' + str(leg_id) + '/' + VOICE_RECORDINGS_PATH + uri = self.generate_voice_calls_url(call_id=call_id, leg_id=leg_id) return VoiceRecordingsList().load(self.request(uri, 'GET')) def voice_transcription_list(self, call_id, leg_id, recording_id): """List voice transcriptions.""" - uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' + str(call_id) + '/' + VOICE_LEGS_PATH + '/' + str(leg_id) + '/' + VOICE_RECORDINGS_PATH + '/' + str(recording_id) + '/' + VOICE_TRANSCRIPTIONS_PATH + uri = self.generate_voice_calls_url(call_id, leg_id, recording_id) return VoiceTranscriptionsList().load(self.request(uri, 'GET')) def voice_transcription_download(self, call_id, leg_id, recording_id, transcriptions_file): """Download voice transcription file.""" - uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' + str(call_id) + '/' + VOICE_LEGS_PATH + '/' + str(leg_id) + '/' + VOICE_RECORDINGS_PATH + '/' + str(recording_id) + '/' + VOICE_TRANSCRIPTIONS_PATH + '/' + str(transcriptions_file) + uri = self.generate_voice_calls_url(call_id, leg_id, recording_id) + '/' + str(transcriptions_file) return self.request(uri, 'GET') def voice_transcription_view(self, call_id, leg_id, recording_id, transcriptions_id): """Get voice transcription data.""" - uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' + str(call_id) + '/' + VOICE_LEGS_PATH + '/' + str(leg_id) + '/' + VOICE_RECORDINGS_PATH + '/' + str(recording_id) + '/' + VOICE_TRANSCRIPTIONS_PATH + '/' + str(transcriptions_id) + uri = self.generate_voice_calls_url(call_id, leg_id, recording_id) + '/' + str(transcriptions_id) return VoiceTranscriptionsView().load(self.request(uri, 'GET')) def voice_transcription_create(self, call_id, leg_id, recording_id, language): """Create a voice transcription.""" - uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' + str(call_id) + '/' + VOICE_LEGS_PATH + '/' + str(leg_id) + '/' + VOICE_RECORDINGS_PATH + '/' + str(recording_id) + '/' + VOICE_TRANSCRIPTIONS_PATH + uri = self.generate_voice_calls_url(call_id, leg_id, recording_id) params = {'language': str(language)} return VoiceTranscriptionsView().load(self.request(uri, 'POST', params, VOICE_TYPE)) def voice_recording_view(self, call_id, leg_id, recording_id): - uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' + str(call_id) + '/' + VOICE_LEGS_PATH + '/' + str(leg_id) + '/' + VOICE_RECORDINGS_PATH + '/' + str(recording_id) + uri = self.generate_voice_calls_url(call_id=call_id, leg_id=leg_id) + '/' + str(recording_id) recording_response = self.request(uri, 'GET') recording_links = recording_response.get('_links') if recording_links is not None: @@ -414,7 +425,7 @@ def voice_recording_view(self, call_id, leg_id, recording_id): return VoiceRecording().load(recording_response['data'][0]) def voice_recording_download(self, call_id, leg_id, recording_id): - uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' + str(call_id) + '/' + VOICE_LEGS_PATH + '/' + str(leg_id) + '/' + VOICE_RECORDINGS_PATH + '/' + str(recording_id) + uri = self.generate_voice_calls_url(call_id=call_id, leg_id=leg_id) + '/' + str(recording_id) recording_response = self.request(uri, 'GET') recording_links = recording_response.get('_links') if recording_links is None or recording_links.get('file') is None: @@ -423,6 +434,40 @@ def voice_recording_download(self, call_id, leg_id, recording_id): recording_file = self.request_store_as_file(VOICE_API_ROOT + recording_file, recording_id + '.wav') return VOICE_API_ROOT + recording_file + def voice_read_webhook(self, id): + """ + Retrieve a voice webhook + API Reference: https://developers.messagebird.com/api/voice-calling/#webhooks + """ + uri = VOICE_API_ROOT + '/' + VOICE_WEB_HOOKS_PATH + '/' + str(id) + return VoiceWebhook().load(self.request(uri, 'GET', None, VOICE_TYPE)) + + def voice_list_webhooks(self, limit=10, offset=0): + """ Retrieve a list of voice webhooks. """ + uri = VOICE_API_ROOT + '/' + VOICE_WEB_HOOKS_PATH + '?' + self._format_query(limit, offset) + return VoiceWebhookList().load(self.request(uri, 'GET', None, VOICE_TYPE)) + + def voice_create_webhook(self, create_webhook_request): + """ Create a voice webhook. """ + if create_webhook_request is None: + raise ValidationError('Create request is empty') + + uri = VOICE_API_ROOT + '/' + VOICE_WEB_HOOKS_PATH + return VoiceWebhook().load(self.request(uri, 'POST', create_webhook_request.__dict__(), VOICE_TYPE)) + + def voice_update_webhook(self, id, update_webhook_request): + """ Update a voice webhook. """ + if update_webhook_request is None: + raise ValidationError('Update request is empty') + + uri = VOICE_API_ROOT + '/' + VOICE_WEB_HOOKS_PATH + '/' + str(id) + return VoiceWebhook().load(self.request(uri, 'PUT', update_webhook_request.__dict__(), VOICE_TYPE)) + + def voice_delete_webhook(self, id): + """ Delete a voice webhook. """ + uri = VOICE_API_ROOT + '/' + VOICE_WEB_HOOKS_PATH + '/' + str(id) + self.request(uri, 'DELETE', None, VOICE_TYPE) + def call_flow(self, id): return CallFlow().load(self.request('call-flows/' + str(id), 'GET', None, VOICE_TYPE)) @@ -442,11 +487,21 @@ def call_flow_delete(self, id): self.request_plain_text('call-flows/' + str(id), 'DELETE', None, VOICE_TYPE) def call_flow_numbers_list(self, call_flow_id): - return CallFlowNumberList().load(self.request('call-flows/' + str(call_flow_id) + '/numbers', 'GET', None, VOICE_TYPE)) + return CallFlowNumberList().load( + self.request('call-flows/' + str(call_flow_id) + '/numbers', 'GET', None, VOICE_TYPE)) def call_flow_numbers_add(self, call_flow_id, numbers=()): params = {'numbers': numbers} - return CallFlowNumberList().load(self.request('call-flows/' + str(call_flow_id) + '/numbers', 'POST', params, VOICE_TYPE)) + return CallFlowNumberList().load( + self.request('call-flows/' + str(call_flow_id) + '/numbers', 'POST', params, VOICE_TYPE)) def _format_query(self, limit, offset): return 'limit=' + str(limit) + '&offset=' + str(offset) + + @staticmethod + def generate_voice_calls_url(call_id=None, leg_id=None, recording_id=None): + uri = VOICE_API_ROOT + '/' + VOICE_PATH + '/' + uri += str(call_id) + '/' + VOICE_LEGS_PATH + '/' + str(leg_id) + '/' + VOICE_RECORDINGS_PATH + if recording_id: + uri += '/' + str(recording_id) + '/' + VOICE_TRANSCRIPTIONS_PATH + return uri diff --git a/messagebird/conversation_webhook.py b/messagebird/conversation_webhook.py index 68e924d..4b581d3 100644 --- a/messagebird/conversation_webhook.py +++ b/messagebird/conversation_webhook.py @@ -44,6 +44,7 @@ def __str__(self): 'updated date time : %s' % self.updatedDatetime ]) + class ConversationWebhookList(Base): def __init__(self): self.offset = None diff --git a/messagebird/error.py b/messagebird/error.py index 7084735..e086491 100644 --- a/messagebird/error.py +++ b/messagebird/error.py @@ -1,10 +1,22 @@ from messagebird.base import Base + class Error(Base): - def __init__(self): - self.code = None - self.description = None - self.parameter = None + def __init__(self): + self.code = None + self.description = None + self.parameter = None + + def __str__(self): + return str(dict(code=self.code, description=self.description, parameter=self.parameter)) + + +class ValidationError(ValueError): + """Exception raised for errors in validation. + + Attributes: + message -- explanation of the error + """ - def __str__(self): - return str(dict(code=self.code, description=self.description, parameter=self.parameter)) + def __init__(self, message): + self.message = message diff --git a/messagebird/formats.py b/messagebird/formats.py index c45aa0d..83c7748 100644 --- a/messagebird/formats.py +++ b/messagebird/formats.py @@ -1,8 +1,9 @@ from messagebird.base import Base + class Formats(Base): - def __init__(self): - self.e164 = None - self.international = None - self.national = None - self.rfc3966 = None + def __init__(self): + self.e164 = None + self.international = None + self.national = None + self.rfc3966 = None diff --git a/messagebird/hlr.py b/messagebird/hlr.py index c0623c9..0b41ad0 100644 --- a/messagebird/hlr.py +++ b/messagebird/hlr.py @@ -1,29 +1,30 @@ from messagebird.base import Base + class HLR(Base): - def __init__(self): - self.id = None - self.href = None - self.msisdn = None - self.network = None - self.reference = None - self.status = None - self.details = None - self._createdDatetime = None - self._statusDatetime = None + def __init__(self): + self.id = None + self.href = None + self.msisdn = None + self.network = None + self.reference = None + self.status = None + self.details = None + self._createdDatetime = None + self._statusDatetime = None - @property - def createdDatetime(self): - return self._createdDatetime + @property + def createdDatetime(self): + return self._createdDatetime - @createdDatetime.setter - def createdDatetime(self, value): - self._createdDatetime = self.value_to_time(value) + @createdDatetime.setter + def createdDatetime(self, value): + self._createdDatetime = self.value_to_time(value) - @property - def statusDatetime(self): - return self._statusDatetime + @property + def statusDatetime(self): + return self._statusDatetime - @statusDatetime.setter - def statusDatetime(self, value): - self._statusDatetime = self.value_to_time(value) + @statusDatetime.setter + def statusDatetime(self, value): + self._statusDatetime = self.value_to_time(value) diff --git a/messagebird/http_client.py b/messagebird/http_client.py index d82afc2..4dcf1e9 100644 --- a/messagebird/http_client.py +++ b/messagebird/http_client.py @@ -1,12 +1,14 @@ -import json import requests from enum import Enum +from messagebird.serde import json_serialize + try: from urllib.parse import urljoin except ImportError: from urlparse import urljoin + class ResponseFormat(Enum): text = 1 binary = 2 @@ -24,7 +26,8 @@ def __init__(self, endpoint, access_key, user_agent): def request(self, path, method='GET', params=None, format=ResponseFormat.text): """Builds a request and gets a response.""" - if params is None: params = {} + if params is None: + params = {} url = urljoin(self.endpoint, path) headers = { 'Accept': 'application/json', @@ -34,15 +37,16 @@ def request(self, path, method='GET', params=None, format=ResponseFormat.text): } method_switcher = { - 'DELETE': requests.delete(url, verify=True, headers=headers, data=json.dumps(params)), - 'GET': requests.get(url, verify=True, headers=headers, params=params), - 'PATCH': requests.patch(url, verify=True, headers=headers, data=json.dumps(params)), - 'POST': requests.post(url, verify=True, headers=headers, data=json.dumps(params)), - 'PUT': requests.put(url, verify=True, headers=headers, data=json.dumps(params)) + 'DELETE': lambda: requests.delete(url, verify=True, headers=headers, data=json_serialize(params)), + 'GET': lambda: requests.get(url, verify=True, headers=headers, params=params), + 'PATCH': lambda: requests.patch(url, verify=True, headers=headers, data=json_serialize(params)), + 'POST': lambda: requests.post(url, verify=True, headers=headers, data=json_serialize(params)), + 'PUT': lambda: requests.put(url, verify=True, headers=headers, data=json_serialize(params)) } - response = method_switcher.get(method, str(method) + ' is not a supported HTTP method') - if isinstance(response, str): - raise ValueError(response) + if method not in method_switcher: + raise ValueError(str(method) + ' is not a supported HTTP method') + + response = method_switcher[method]() if response.status_code not in self.__supported_status_codes: response.raise_for_status() diff --git a/messagebird/lookup.py b/messagebird/lookup.py index 006eb77..e3c9994 100644 --- a/messagebird/lookup.py +++ b/messagebird/lookup.py @@ -1,32 +1,33 @@ -from messagebird.base import Base +from messagebird.base import Base from messagebird.formats import Formats -from messagebird.hlr import HLR +from messagebird.hlr import HLR + class Lookup(Base): - def __init__(self): - self.href = None - self.countryCode = None - self.countryPrefix = None - self.phoneNumber = None - self.type = None - self._formats = None - self._hlr = None + def __init__(self): + self.href = None + self.countryCode = None + self.countryPrefix = None + self.phoneNumber = None + self.type = None + self._formats = None + self._hlr = None - def __str__(self): - return str(self.__class__) + ": " + str(self.__dict__) + def __str__(self): + return str(self.__class__) + ": " + str(self.__dict__) - @property - def formats(self): - return self._formats + @property + def formats(self): + return self._formats - @formats.setter - def formats(self, value): - self._formats = Formats().load(value) + @formats.setter + def formats(self, value): + self._formats = Formats().load(value) - @property - def hlr(self): - return self._hlr + @property + def hlr(self): + return self._hlr - @hlr.setter - def hlr(self, value): - self._hlr = HLR().load(value) + @hlr.setter + def hlr(self, value): + self._hlr = HLR().load(value) diff --git a/messagebird/message.py b/messagebird/message.py index 24b1fbf..33e60f2 100644 --- a/messagebird/message.py +++ b/messagebird/message.py @@ -1,4 +1,4 @@ -from messagebird.base import Base +from messagebird.base import Base from messagebird.base_list import BaseList from messagebird.recipient import Recipient @@ -10,44 +10,44 @@ def __init__(self): class Message(Base): - def __init__(self): - self.id = None - self.href = None - self.direction = None - self.type = None - self.originator = None - self.body = None - self.reference = None - self.validity = None - self.gateway = None - self.typeDetails = None - self.datacoding = None - self.mclass = None - self._scheduledDatetime = None - self._createdDatetime = None - self._recipients = None - - @property - def scheduledDatetime(self): - return self._scheduledDatetime - - @scheduledDatetime.setter - def scheduledDatetime(self, value): - self._scheduledDatetime = self.value_to_time(value) - - @property - def createdDatetime(self): - return self._createdDatetime - - @createdDatetime.setter - def createdDatetime(self, value): - self._createdDatetime = self.value_to_time(value) - - @property - def recipients(self): - return self._recipients - - @recipients.setter - def recipients(self, value): - value['items'] = [Recipient().load(r) for r in value['items']] - self._recipients = value + def __init__(self): + self.id = None + self.href = None + self.direction = None + self.type = None + self.originator = None + self.body = None + self.reference = None + self.validity = None + self.gateway = None + self.typeDetails = None + self.datacoding = None + self.mclass = None + self._scheduledDatetime = None + self._createdDatetime = None + self._recipients = None + + @property + def scheduledDatetime(self): + return self._scheduledDatetime + + @scheduledDatetime.setter + def scheduledDatetime(self, value): + self._scheduledDatetime = self.value_to_time(value) + + @property + def createdDatetime(self): + return self._createdDatetime + + @createdDatetime.setter + def createdDatetime(self, value): + self._createdDatetime = self.value_to_time(value) + + @property + def recipients(self): + return self._recipients + + @recipients.setter + def recipients(self, value): + value['items'] = [Recipient().load(r) for r in value['items']] + self._recipients = value diff --git a/messagebird/mms.py b/messagebird/mms.py index 39ffe97..aa4682a 100644 --- a/messagebird/mms.py +++ b/messagebird/mms.py @@ -1,56 +1,57 @@ -from messagebird.base import Base +from messagebird.base import Base from messagebird.recipient import Recipient + class MMS(Base): - def __init__(self): - self.id = None - self.href = None - self.direction = None - self.originator = None - self.subject = None - self.body = None - self.mediaUrls = None - self.reference = None - self._scheduledDatetime = None - self._createdDatetime = None - self._recipients = None - - @property - def scheduledDatetime(self): - return self._scheduledDatetime - - @scheduledDatetime.setter - def scheduledDatetime(self, value): - self._scheduledDatetime = self.value_to_time(value) - - @property - def createdDatetime(self): - return self._createdDatetime - - @createdDatetime.setter - def createdDatetime(self, value): - self._createdDatetime = self.value_to_time(value) - - @property - def recipients(self): - return self._recipients - - @recipients.setter - def recipients(self, value): - value['items'] = [Recipient().load(r) for r in value['items']] - self._recipients = value - - def __str__(self): - return "\n".join([ - "id : %s" % self.id , - "href : %s" % self.href , - "direction : %s" % self.direction , - "originator : %s" % self.originator , - "subject : %s" % self.subject , - "body : %s" % self.body , - "mediaUrls : %s" % ",".join(self.mediaUrls), - "reference : %s" % self.reference , - "scheduledDatetime : %s" % self.scheduledDatetime , - "createdDatetime : %s" % self.createdDatetime , - "recipients : %s" % self.recipients, - ]) \ No newline at end of file + def __init__(self): + self.id = None + self.href = None + self.direction = None + self.originator = None + self.subject = None + self.body = None + self.mediaUrls = None + self.reference = None + self._scheduledDatetime = None + self._createdDatetime = None + self._recipients = None + + @property + def scheduledDatetime(self): + return self._scheduledDatetime + + @scheduledDatetime.setter + def scheduledDatetime(self, value): + self._scheduledDatetime = self.value_to_time(value) + + @property + def createdDatetime(self): + return self._createdDatetime + + @createdDatetime.setter + def createdDatetime(self, value): + self._createdDatetime = self.value_to_time(value) + + @property + def recipients(self): + return self._recipients + + @recipients.setter + def recipients(self, value): + value['items'] = [Recipient().load(r) for r in value['items']] + self._recipients = value + + def __str__(self): + return "\n".join([ + "id : %s" % self.id, + "href : %s" % self.href, + "direction : %s" % self.direction, + "originator : %s" % self.originator, + "subject : %s" % self.subject, + "body : %s" % self.body, + "mediaUrls : %s" % ",".join(self.mediaUrls), + "reference : %s" % self.reference, + "scheduledDatetime : %s" % self.scheduledDatetime, + "createdDatetime : %s" % self.createdDatetime, + "recipients : %s" % self.recipients, + ]) diff --git a/messagebird/recipient.py b/messagebird/recipient.py index 1764e5f..4064159 100644 --- a/messagebird/recipient.py +++ b/messagebird/recipient.py @@ -1,15 +1,16 @@ from messagebird.base import Base + class Recipient(Base): - def __init__(self): - self.recipient = None - self.status = None - self._statusDatetime = None - - @property - def statusDatetime(self): - return self._statusDatetime - - @statusDatetime.setter - def statusDatetime(self, value): - self._statusDatetime = self.value_to_time(value) + def __init__(self): + self.recipient = None + self.status = None + self._statusDatetime = None + + @property + def statusDatetime(self): + return self._statusDatetime + + @statusDatetime.setter + def statusDatetime(self, value): + self._statusDatetime = self.value_to_time(value) diff --git a/messagebird/serde.py b/messagebird/serde.py new file mode 100644 index 0000000..cd86c1e --- /dev/null +++ b/messagebird/serde.py @@ -0,0 +1,8 @@ +import json + + +def json_serialize(obj): + try: + return json.dumps(obj) + except TypeError: + return json.dumps(obj, default=lambda o: o.__dict__) diff --git a/messagebird/validation.py b/messagebird/validation.py new file mode 100644 index 0000000..0a63eb2 --- /dev/null +++ b/messagebird/validation.py @@ -0,0 +1,6 @@ +from messagebird.error import ValidationError + + +def validate_is_not_blank(value, message): + if value is None or not value.strip(): + raise ValidationError(message) diff --git a/messagebird/verify.py b/messagebird/verify.py index 972643b..299b645 100644 --- a/messagebird/verify.py +++ b/messagebird/verify.py @@ -1,30 +1,29 @@ from messagebird.base import Base -class Verify(Base): - def __init__(self): - self.id = None - self.href = None - self.recipient = None - self.reference = None - self.messages = None - self.status = None - self._createdDatetime = None - self._validUntilDatetime = None - - @property - def createdDatetime(self): - return self._createdDatetime +class Verify(Base): + def __init__(self): + self.id = None + self.href = None + self.recipient = None + self.reference = None + self.messages = None + self.status = None + self._createdDatetime = None + self._validUntilDatetime = None - @createdDatetime.setter - def createdDatetime(self, value): - self._createdDatetime = self.value_to_time(value) + @property + def createdDatetime(self): + return self._createdDatetime + @createdDatetime.setter + def createdDatetime(self, value): + self._createdDatetime = self.value_to_time(value) - @property - def validUntilDatetime(self): - return self._validUntilDatetime + @property + def validUntilDatetime(self): + return self._validUntilDatetime - @validUntilDatetime.setter - def validUntilDatetime(self, value): - self._validUntilDatetime = self.value_to_time(value) + @validUntilDatetime.setter + def validUntilDatetime(self, value): + self._validUntilDatetime = self.value_to_time(value) diff --git a/messagebird/voice_recording.py b/messagebird/voice_recording.py index a1c228e..22db896 100644 --- a/messagebird/voice_recording.py +++ b/messagebird/voice_recording.py @@ -1,5 +1,6 @@ from messagebird.base import Base + class VoiceRecording(Base): def __init__(self): @@ -44,6 +45,7 @@ def __str__(self): 'links : %s' % self._links ]) + class VoiceRecordingsList(Base): def __init__(self): self._items = None @@ -55,7 +57,7 @@ def data(self): @data.setter def data(self, value): if isinstance(value, list): - self._items = [] + self._items = [] for item in value: self._items.append(VoiceRecording().load(item)) diff --git a/messagebird/voice_transcription.py b/messagebird/voice_transcription.py index 96a290e..0880866 100644 --- a/messagebird/voice_transcription.py +++ b/messagebird/voice_transcription.py @@ -51,4 +51,4 @@ def data(self): @data.setter def data(self, value): if isinstance(value, list): - self.items = value \ No newline at end of file + self.items = value diff --git a/messagebird/voice_webhook.py b/messagebird/voice_webhook.py new file mode 100644 index 0000000..7d752b7 --- /dev/null +++ b/messagebird/voice_webhook.py @@ -0,0 +1,129 @@ +from messagebird.base import Base +from messagebird.validation import validate_is_not_blank + + +class VoiceWebhook(Base): + def __init__(self): + self.id = None + self.url = None + self.token = None + self._createdAtDatetime = None + self._updatedAtDatetime = None + self._links = None + + @property + def createdAtDatetime(self): + return self._createdAtDatetime + + @createdAtDatetime.setter + def createdAt(self, value): + if value is not None: + self._createdAtDatetime = self.value_to_time(value, '%Y-%m-%dT%H:%M:%SZ') + + @property + def updatedAtDatetime(self): + return self._updatedAtDatetime + + @updatedAtDatetime.setter + def updatedAt(self, value): + if value is not None: + self._updatedAtDatetime = self.value_to_time(value, '%Y-%m-%dT%H:%M:%SZ') + + def load(self, data): + if data.get('data') is not None: + items = data.get('data')[0].items() + else: + items = list(data.items()) + + for name, value in items: + if hasattr(self, name) and not callable(getattr(self, name)): + setattr(self, name, value) + + return self + + def __str__(self): + return "\n".join([ + 'webhook id : %s' % self.id, + 'url : %s' % self.url, + 'token : %s' % self.token, + 'createdAtDatetime : %s' % self._createdAtDatetime, + 'updatedAtDatetime : %s' % self._updatedAtDatetime, + 'links : %s' % self._links + ]) + + +class VoiceWebhookList(Base): + def __init__(self): + self._items = None + + @property + def data(self): + return self._items + + @data.setter + def data(self, value): + if isinstance(value, list): + self._items = [] + for item in value: + self._items.append(VoiceWebhook().load(item)) + + def __str__(self): + item_ids = [] + if self._items is not None: + for voice_item in self._items: + item_ids.append(voice_item.id) + + return "\n".join([ + 'items IDs : %s' % item_ids, + 'count : %s' % len(item_ids) + ]) + + +class VoiceCreateWebhookRequest(Base): + def __init__(self, title=None, url=None, token=None): + validate_is_not_blank(url, "url cannot be empty") + self.title = title + self._url = url + self.token = token + + @property + def url(self): + return self._url + + @url.setter + def url(self, value): + validate_is_not_blank(value, "url cannot be empty") + self._url = value + + def __dict__(self): + return { + 'title': self.title, + 'url': self.url, + 'token': self.token + } + + def __str__(self): + return "\n".join([ + 'title : %s' % self.title, + 'url : %s' % self.url, + 'token : %s' % self.token, + ]) + + +class VoiceUpdateWebhookRequest(Base): + + def __init__(self, title=None, token=None): + self.title = title + self.token = token + + def __dict__(self): + return { + 'title': self.title, + 'token': self.token + } + + def __str__(self): + return "\n".join([ + 'title : %s' % self.title, + 'token : %s' % self.token, + ]) diff --git a/messagebird/voicemessage.py b/messagebird/voicemessage.py index a8630d4..27a5a1f 100644 --- a/messagebird/voicemessage.py +++ b/messagebird/voicemessage.py @@ -1,42 +1,43 @@ -from messagebird.base import Base +from messagebird.base import Base from messagebird.recipient import Recipient + class VoiceMessage(Base): - def __init__(self): - self.id = None - self.href = None - self.originator = None - self.body = None - self.reference = None - self.language = None - self.voice = None - self.repeat = None - self.ifMachine = None - self._scheduledDatetime = None - self._createdDatetime = None - self._recipients = None - - @property - def scheduledDatetime(self): - return self._scheduledDatetime - - @scheduledDatetime.setter - def scheduledDatetime(self, value): - self._scheduledDatetime = self.value_to_time(value) - - @property - def createdDatetime(self): - return self._createdDatetime - - @createdDatetime.setter - def createdDatetime(self, value): - self._createdDatetime = self.value_to_time(value) - - @property - def recipients(self): - return self._recipients - - @recipients.setter - def recipients(self, value): - value['items'] = [Recipient().load(r) for r in value['items']] - self._recipients = value + def __init__(self): + self.id = None + self.href = None + self.originator = None + self.body = None + self.reference = None + self.language = None + self.voice = None + self.repeat = None + self.ifMachine = None + self._scheduledDatetime = None + self._createdDatetime = None + self._recipients = None + + @property + def scheduledDatetime(self): + return self._scheduledDatetime + + @scheduledDatetime.setter + def scheduledDatetime(self, value): + self._scheduledDatetime = self.value_to_time(value) + + @property + def createdDatetime(self): + return self._createdDatetime + + @createdDatetime.setter + def createdDatetime(self, value): + self._createdDatetime = self.value_to_time(value) + + @property + def recipients(self): + return self._recipients + + @recipients.setter + def recipients(self, value): + value['items'] = [Recipient().load(r) for r in value['items']] + self._recipients = value diff --git a/messagebird/webhook.py b/messagebird/webhook.py index 28c0e7b..3516e82 100644 --- a/messagebird/webhook.py +++ b/messagebird/webhook.py @@ -8,7 +8,7 @@ def __init__(self): self.token = None def __str__(self): - return "\n".join([ + return "\n".join([ 'url : %s' % self.url, 'token : %s' % self.token, ]) diff --git a/tests/test_call_flow.py b/tests/test_call_flow.py index 53503a2..fe06886 100644 --- a/tests/test_call_flow.py +++ b/tests/test_call_flow.py @@ -14,28 +14,28 @@ class TestCallFlow(unittest.TestCase): def test_get_flow(self): http_client = Mock() http_client.request.return_value = '''{ - "data": [ - { - "id": "de3ed163-d5fc-45f4-b8c4-7eea7458c635", - "title": "Updated call flow", - "record": false, - "steps": [ - { - "id": "3538a6b8-5a2e-4537-8745-f72def6bd393", - "action": "transfer", - "options": { - "destination": "31611223344" + "data": [ + { + "id": "de3ed163-d5fc-45f4-b8c4-7eea7458c635", + "title": "Updated call flow", + "record": false, + "steps": [ + { + "id": "3538a6b8-5a2e-4537-8745-f72def6bd393", + "action": "transfer", + "options": { + "destination": "31611223344" + } + } + ], + "createdAt": "2017-03-06T13:34:14Z", + "updatedAt": "2017-03-06T15:02:38Z" + } + ], + "_links": { + "self": "/call-flows/de3ed163-d5fc-45f4-b8c4-7eea7458c635" } } - ], - "createdAt": "2017-03-06T13:34:14Z", - "updatedAt": "2017-03-06T15:02:38Z" - } - ], - "_links": { - "self": "/call-flows/de3ed163-d5fc-45f4-b8c4-7eea7458c635" - } -} ''' call_flow = Client('', http_client).call_flow('de3ed163-d5fc-45f4-b8c4-7eea7458c635') @@ -47,56 +47,56 @@ def test_get_flow(self): def test_get_flow_list(self): http_client = Mock() http_client.request.return_value = '''{ - "data": [ - { - "id": "de3ed163-d5fc-45f4-b8c4-7eea7458c635", - "title": "Forward call to 31612345678", - "record": false, - "steps": [ - { - "id": "3538a6b8-5a2e-4537-8745-f72def6bd393", - "action": "transfer", - "options": { - "destination": "31612345678" + "data": [ + { + "id": "de3ed163-d5fc-45f4-b8c4-7eea7458c635", + "title": "Forward call to 31612345678", + "record": false, + "steps": [ + { + "id": "3538a6b8-5a2e-4537-8745-f72def6bd393", + "action": "transfer", + "options": { + "destination": "31612345678" + } + } + ], + "createdAt": "2017-03-06T13:34:14Z", + "updatedAt": "2017-03-06T13:34:14Z", + "_links": { + "self": "/call-flows/de3ed163-d5fc-45f4-b8c4-7eea7458c635" + } + }, + { + "id": "de3ed163-d5fc-45f4-b8c4-7eea7458c634", + "title": "Forward call to 0600123123", + "record": true, + "steps": [ + { + "id": "3538a6b8-5a2e-4537-8745-f72def6bd393", + "action": "transfer", + "options": { + "destination": "31612345678" + } + } + ], + "createdAt": "2017-03-06T13:34:14Z", + "updatedAt": "2017-03-06T13:34:14Z", + "_links": { + "self": "/call-flows/de3ed163-d5fc-45f4-b8c4-7eea7458c634" + } + } + ], + "_links": { + "self": "/call-flows?page=1" + }, + "pagination": { + "totalCount": 2, + "pageCount": 2, + "currentPage": 1, + "perPage": 10 } } - ], - "createdAt": "2017-03-06T13:34:14Z", - "updatedAt": "2017-03-06T13:34:14Z", - "_links": { - "self": "/call-flows/de3ed163-d5fc-45f4-b8c4-7eea7458c635" - } - }, - { - "id": "de3ed163-d5fc-45f4-b8c4-7eea7458c634", - "title": "Forward call to 0600123123", - "record": true, - "steps": [ - { - "id": "3538a6b8-5a2e-4537-8745-f72def6bd393", - "action": "transfer", - "options": { - "destination": "31612345678" - } - } - ], - "createdAt": "2017-03-06T13:34:14Z", - "updatedAt": "2017-03-06T13:34:14Z", - "_links": { - "self": "/call-flows/de3ed163-d5fc-45f4-b8c4-7eea7458c634" - } - } - ], - "_links": { - "self": "/call-flows?page=1" - }, - "pagination": { - "totalCount": 2, - "pageCount": 2, - "currentPage": 1, - "perPage": 10 - } -} ''' call_flow_list = Client('', http_client).call_flow_list(20, 0) @@ -109,33 +109,34 @@ def test_get_flow_list(self): def test_numbers_list(self): http_client = Mock() http_client.request.return_value = '''{ - "data": [ - { - "id": "13f38f34-7ff4-45b3-8783-8d5b1143f22b", - "number": "31611111111", - "callFlowId": "de3ed163-d5fc-45f4-b8c4-7eea7458c635", - "createdAt": "2017-03-16T13:49:24Z", - "updatedAt": "2017-09-12T08:59:50Z", - "_links": { - "self": "/numbers/13f38f34-7ff4-45b3-8783-8d5b1143f22b" - } - } - ], - "_links": { - "self": "/call-flows/de3ed163-d5fc-45f4-b8c4-7eea7458c635/numbers?page=1" - }, - "pagination": { - "totalCount": 1, - "pageCount": 1, - "currentPage": 1, - "perPage": 10 - } -} + "data": [ + { + "id": "13f38f34-7ff4-45b3-8783-8d5b1143f22b", + "number": "31611111111", + "callFlowId": "de3ed163-d5fc-45f4-b8c4-7eea7458c635", + "createdAt": "2017-03-16T13:49:24Z", + "updatedAt": "2017-09-12T08:59:50Z", + "_links": { + "self": "/numbers/13f38f34-7ff4-45b3-8783-8d5b1143f22b" + } + } + ], + "_links": { + "self": "/call-flows/de3ed163-d5fc-45f4-b8c4-7eea7458c635/numbers?page=1" + }, + "pagination": { + "totalCount": 1, + "pageCount": 1, + "currentPage": 1, + "perPage": 10 + } + } ''' number_list = Client('', http_client).call_flow_numbers_list('de3ed163-d5fc-45f4-b8c4-7eea7458c635') - http_client.request.assert_called_once_with('call-flows/de3ed163-d5fc-45f4-b8c4-7eea7458c635/numbers', 'GET', None) + http_client.request.assert_called_once_with('call-flows/de3ed163-d5fc-45f4-b8c4-7eea7458c635/numbers', 'GET', + None) self.assertEqual('31611111111', number_list.data[0].number) self.assertEqual(1, number_list.pagination['totalCount']) @@ -144,9 +145,12 @@ def test_numbers_add(self): http_client = Mock() http_client.request.return_value = '{}' - Client('', http_client).call_flow_numbers_add('de3ed163-d5fc-45f4-b8c4-7eea7458c635', - ['31611111111', '31611111112']) + Client('', http_client).call_flow_numbers_add( + 'de3ed163-d5fc-45f4-b8c4-7eea7458c635', + ['31611111111', '31611111112'] + ) params = {'numbers': ['31611111111', '31611111112']} - http_client.request.assert_called_once_with('call-flows/de3ed163-d5fc-45f4-b8c4-7eea7458c635/numbers', 'POST', params) \ No newline at end of file + http_client.request.assert_called_once_with( + 'call-flows/de3ed163-d5fc-45f4-b8c4-7eea7458c635/numbers', 'POST', params) diff --git a/tests/test_contact.py b/tests/test_contact.py index 12c265c..060e163 100644 --- a/tests/test_contact.py +++ b/tests/test_contact.py @@ -30,7 +30,8 @@ def test_contact_create(self): Client('', http_client).contact_create(31612345678, {'firstName': 'Foo', 'custom3': 'Third'}) - http_client.request.assert_called_once_with('contacts', 'POST', {'msisdn': 31612345678, 'firstName': 'Foo', 'custom3': 'Third'}) + http_client.request.assert_called_once_with( + 'contacts', 'POST', {'msisdn': 31612345678, 'firstName': 'Foo', 'custom3': 'Third'}) def test_contact_delete(self): http_client = Mock() @@ -55,7 +56,9 @@ def test_contact_update(self): Client('', http_client).contact_update('contact-id', {'msisdn': 31687654321, 'custom4': 'fourth'}) - http_client.request.assert_called_once_with('contacts/contact-id', 'PATCH', {'msisdn': 31687654321, 'custom4': 'fourth'}) + http_client.request.assert_called_once_with( + 'contacts/contact-id', 'PATCH', {'msisdn': 31687654321, 'custom4': 'fourth'} + ) def test_contact_list(self): http_client = Mock() diff --git a/tests/test_conversation_webhook.py b/tests/test_conversation_webhook.py index f329194..01dc68b 100644 --- a/tests/test_conversation_webhook.py +++ b/tests/test_conversation_webhook.py @@ -70,16 +70,16 @@ def test_conversation_webhook_read(self): def test_conversation_webhook_update(self): http_client = Mock() http_client.request.return_value = json.dumps({"id": "985ae50937a94c64b392531ea87a0263", - "url": "https://example.com/webhook", - "channelId": "853eeb5348e541a595da93b48c61a1ae", - "events": [ - "message.created", - "message.updated", - ], - "status": "enabled", - "createdDatetime": "2018-08-29T10:04:23Z", - "updatedDatetime": "2018-08-29T10:10:23Z" - }) + "url": "https://example.com/webhook", + "channelId": "853eeb5348e541a595da93b48c61a1ae", + "events": [ + "message.created", + "message.updated", + ], + "status": "enabled", + "createdDatetime": "2018-08-29T10:04:23Z", + "updatedDatetime": "2018-08-29T10:10:23Z" + }) webhookRequestData = { 'events': [CONVERSATION_WEBHOOK_EVENT_CONVERSATION_CREATED, @@ -88,4 +88,4 @@ def test_conversation_webhook_update(self): 'status': 'enabled' } web_hook = Client('', http_client).conversation_update_webhook('webhook-id', webhookRequestData) - http_client.request.assert_called_once_with('webhooks/webhook-id', 'PATCH', webhookRequestData) \ No newline at end of file + http_client.request.assert_called_once_with('webhooks/webhook-id', 'PATCH', webhookRequestData) diff --git a/tests/test_group.py b/tests/test_group.py index b75d109..581ee82 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -64,7 +64,8 @@ def test_group_add_contacts(self): Client('', http_client).group_add_contacts('group-id', ['contact-id', 'other-contact-id']) - http_client.request.assert_called_once_with('groups/group-id?ids[]=contact-id&ids[]=other-contact-id', 'PUT', None) + http_client.request.assert_called_once_with('groups/group-id?ids[]=contact-id&ids[]=other-contact-id', 'PUT', + None) def test_group_remove_contact(self): http_client = Mock() diff --git a/tests/test_message.py b/tests/test_message.py index 6bd0d3b..38013da 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -25,9 +25,18 @@ def test_message_create(self): http_client = Mock() http_client.request.return_value = '{}' - Client('', http_client).message_create('MessageBird', ['31612345678', '31687654321'], 'Hello World', {'datacoding': 'unicode'}) - - http_client.request.assert_called_once_with('messages', 'POST', {'datacoding': 'unicode', 'originator': 'MessageBird', 'body': 'Hello World', 'recipients': '31612345678,31687654321' }) + Client('', http_client).message_create( + 'MessageBird', ['31612345678', '31687654321'], 'Hello World', {'datacoding': 'unicode'}) + + http_client.request.assert_called_once_with( + 'messages', 'POST', + { + 'datacoding': 'unicode', + 'originator': 'MessageBird', + 'body': 'Hello World', + 'recipients': '31612345678,31687654321' + } + ) def test_message_delete(self): http_client = Mock() diff --git a/tests/test_mms.py b/tests/test_mms.py index 61ba3b0..6685458 100644 --- a/tests/test_mms.py +++ b/tests/test_mms.py @@ -15,7 +15,12 @@ def test_create_mms(self): http_client = Mock() http_client.request.return_value = '{"originator": "test-org", "body": "Rich test message", "direction": "mt", "recipients": {"totalCount": 1, "totalSentCount": 1, "totalDeliveredCount": 0, "totalDeliveryFailedCount": 0, "items": [{"status": "sent", "statusDatetime": "2019-06-04T13:54:48+00:00", "recipient": 4915238456487}]}, "reference": null, "createdDatetime": "2019-06-04T13:54:48+00:00", "href": "https://rest.messagebird.com/mms/0a75f8f82b5d4377bd8fb5b22ac1e8ac", "mediaUrls": ["https://www.messagebird.com/assets/images/og/messagebird.gif"], "scheduledDatetime": null, "id": "0a75f8f82b5d4377bd8fb5b22ac1e8ac", "subject": null}' - params = {"originator": "test-org", "body": "Rich test message","recipients":"+4915238456487","mediaUrls":"https://www.messagebird.com/assets/images/og/messagebird.gif"} + params = { + "originator": "test-org", + "body": "Rich test message", + "recipients": "+4915238456487", + "mediaUrls": "https://www.messagebird.com/assets/images/og/messagebird.gif" + } mms = Client('', http_client).mms_create(**params) params["mediaUrls"] = [params["mediaUrls"]] @@ -24,4 +29,4 @@ def test_create_mms(self): self.assertEqual(params["originator"], mms.originator) self.assertEqual(params["recipients"].strip("+"), str(mms.recipients["items"][0].recipient)) - self.assertEqual(1,len(mms.recipients["items"])) + self.assertEqual(1, len(mms.recipients["items"])) diff --git a/tests/test_verify.py b/tests/test_verify.py index cd46ae9..da9d215 100644 --- a/tests/test_verify.py +++ b/tests/test_verify.py @@ -55,4 +55,3 @@ def test_verify_delete_invalid(self): Client('', http_client).verify_delete('non-existent-verify-id') http_client.request.assert_called_once_with('verify/non-existent-verify-id', 'DELETE', None) - diff --git a/tests/test_voice_recording.py b/tests/test_voice_recording.py index d140b72..628bfa2 100644 --- a/tests/test_voice_recording.py +++ b/tests/test_voice_recording.py @@ -19,9 +19,15 @@ def test_voice_recording_view(self): http_client = Mock() http_client.request.return_value = '{"data":[{"id":"12345678-9012-3456-7890-123456789012","format":"wav","legId":"87654321-0987-6543-2109-876543210987","status":"done","duration":32,"type":"transfer","createdAt":"2018-01-01T00:00:01Z","updatedAt":"2018-01-01T00:00:05Z","deletedAt":null}],"_links":{"file":"/calls/12348765-4321-0987-6543-210987654321/legs/87654321-0987-6543-2109-876543210987/recordings/12345678-9012-3456-7890-123456789012.wav","self":"/calls/12345678-9012-3456-7890-123456789012/legs/12348765-4321-0987-6543-210987654321/recordings/12345678-9012-3456-7890-123456789012"},"pagination":{"totalCount":0,"pageCount":0,"currentPage":0,"perPage":0}}' - voice_recording = Client('', http_client).voice_recording_view('12348765-4321-0987-6543-210987654321', '87654321-0987-6543-2109-876543210987', '12345678-9012-3456-7890-123456789012') + voice_recording = Client('', http_client).voice_recording_view( + '12348765-4321-0987-6543-210987654321', + '87654321-0987-6543-2109-876543210987', + '12345678-9012-3456-7890-123456789012' + ) - http_client.request.assert_called_once_with('https://voice.messagebird.com/calls/12348765-4321-0987-6543-210987654321/legs/87654321-0987-6543-2109-876543210987/recordings/12345678-9012-3456-7890-123456789012', 'GET', None) + http_client.request.assert_called_once_with( + 'https://voice.messagebird.com/calls/12348765-4321-0987-6543-210987654321/legs/87654321-0987-6543-2109-876543210987/recordings/12345678-9012-3456-7890-123456789012', + 'GET', None) self.assertEqual('12345678-9012-3456-7890-123456789012', voice_recording.id) self.assertEqual('done', voice_recording.status) @@ -35,13 +41,24 @@ def test_voice_recording_list(self): http_client = Mock() http_client.request.return_value = '{"data":[{"id":"12345678-9012-3456-7890-123456789012","format":"wav","legId":"87654321-0987-6543-2109-876543210987","status":"done","duration":32,"type":"transfer","createdAt":"2018-01-01T00:00:01Z","updatedAt":"2018-01-01T00:00:05Z","deletedAt":null,"_links":{"file":"/calls/12348765-4321-0987-6543-210987654321/legs/7654321-0987-6543-2109-876543210987/recordings/12345678-9012-3456-7890-123456789012.wav","self":"/calls/12348765-4321-0987-6543-210987654321/legs/7654321-0987-6543-2109-876543210987/recordings/12345678-9012-3456-7890-123456789012"}},{"id":"12345678-9012-3456-7890-123456789013","format":"wav","legId":"87654321-0987-6543-2109-876543210987","status":"done","duration":12,"type":"transfer","createdAt":"2019-01-01T00:00:01Z","updatedAt":"2019-01-01T00:00:05Z","deletedAt":null,"_links":{"file":"/calls/12348765-4321-0987-6543-210987654321/legs/7654321-0987-6543-2109-876543210987/recordings/12345678-9012-3456-7890-123456789013.wav","self":"/calls/12348765-4321-0987-6543-210987654321/legs/7654321-0987-6543-2109-876543210987/recordings/12345678-9012-3456-7890-123456789013"}}],"_links":{"self":"/calls/12348765-4321-0987-6543-210987654321/legs/7654321-0987-6543-2109-876543210987/recordings?page=1"},"pagination":{"totalCount":2,"pageCount":1,"currentPage":1,"perPage":10}}' - voice_recordings = Client('', http_client).voice_recording_list_recordings('12348765-4321-0987-6543-210987654321', '87654321-0987-6543-2109-876543210987') + voice_recordings = Client('', http_client).voice_recording_list_recordings( + '12348765-4321-0987-6543-210987654321', '87654321-0987-6543-2109-876543210987') - http_client.request.assert_called_once_with('https://voice.messagebird.com/calls/12348765-4321-0987-6543-210987654321/legs/87654321-0987-6543-2109-876543210987/recordings', 'GET', None) + http_client.request.assert_called_once_with( + 'https://voice.messagebird.com/calls/12348765-4321-0987-6543-210987654321/legs/87654321-0987-6543-2109-876543210987/recordings', + 'GET', None) recordings_check = { - '12345678-9012-3456-7890-123456789012': { "id": '12345678-9012-3456-7890-123456789012', "duration": 32, "year": 2018 }, - '12345678-9012-3456-7890-123456789013': { "id": '12345678-9012-3456-7890-123456789013', "duration": 12, "year": 2019 } + '12345678-9012-3456-7890-123456789012': { + "id": '12345678-9012-3456-7890-123456789012', + "duration": 32, + "year": 2018 + }, + '12345678-9012-3456-7890-123456789013': { + "id": '12345678-9012-3456-7890-123456789013', + "duration": 12, + "year": 2019 + } } for item in voice_recordings._items: @@ -60,13 +77,16 @@ def test_voice_recording_download(self): http_client.request.return_value = '{"data":null,"errors":[{"message":"No recording found for ID `00000000-0000-0000-0000-000000000000`.","code":13}],"pagination":{"totalCount":0,"pageCount":0,"currentPage":0,"perPage":0}}' with self.assertRaises(ErrorException): - voice_recording = Client('', http_client).voice_recording_download('12348765-4321-0987-6543-210987654321', '87654321-0987-6543-2109-876543210987', '12345678-9012-3456-7890-123456789012') + voice_recording = Client('', http_client).voice_recording_download('12348765-4321-0987-6543-210987654321', + '87654321-0987-6543-2109-876543210987', + '12345678-9012-3456-7890-123456789012') http_client.request.return_value = '{"data":[{"id":"12345678-9012-3456-7890-123456789012","format":"wav","legId":"87654321-0987-6543-2109-876543210987","status":"done","duration":32,"type":"transfer","createdAt":"2018-01-01T00:00:01Z","updatedAt":"2018-01-01T00:00:05Z","deletedAt":null}],"pagination":{"totalCount":0,"pageCount":0,"currentPage":0,"perPage":0}}' with self.assertRaises(ErrorException): - voice_recording = Client('', http_client).voice_recording_download('12348765-4321-0987-6543-210987654321', '87654321-0987-6543-2109-876543210987', '12345678-9012-3456-7890-123456789012') - + voice_recording = Client('', http_client).voice_recording_download('12348765-4321-0987-6543-210987654321', + '87654321-0987-6543-2109-876543210987', + '12345678-9012-3456-7890-123456789012') if __name__ == '__main__': diff --git a/tests/test_voice_transcription.py b/tests/test_voice_transcription.py index 60f72b5..1383785 100644 --- a/tests/test_voice_transcription.py +++ b/tests/test_voice_transcription.py @@ -156,4 +156,4 @@ def test_download_voice_transcription(self): url + call_id + '/legs/' + leg_id + '/recordings/' + recording_id + '/transcriptions/' + transcription_id, 'GET', None - ) \ No newline at end of file + ) diff --git a/tests/test_voice_webhook.py b/tests/test_voice_webhook.py new file mode 100644 index 0000000..f2b16c3 --- /dev/null +++ b/tests/test_voice_webhook.py @@ -0,0 +1,176 @@ +import json +import unittest + +from messagebird import Client +from messagebird.client import VOICE_WEB_HOOKS_PATH, VOICE_API_ROOT +from messagebird.error import ValidationError +from messagebird.serde import json_serialize +from messagebird.voice_webhook import VoiceCreateWebhookRequest, VoiceUpdateWebhookRequest + +try: + from unittest.mock import Mock +except ImportError: + # mock was added to unittest in Python 3.3, but was an external library + # before. + from mock import Mock + + +class TestVoiceWebhook(unittest.TestCase): + + def test_voice_read_webhook(self): + http_client = Mock() + http_client.request.return_value = '''{ + "data": [ + { + "id": "534e1848-235f-482d-983d-e3e11a04f58a", + "url": "https://example.com/", + "token": "foobar", + "createdAt": "2017-03-15T13:28:32Z", + "updatedAt": "2017-03-15T13:28:32Z" + } + ], + "_links": { + "self": "/webhooks/534e1848-235f-482d-983d-e3e11a04f58a" + } + }''' + webhook_id = '534e1848-235f-482d-983d-e3e11a04f58a' + webhook_token = 'foobar' + + voice_webhook = Client('', http_client).voice_read_webhook(webhook_id) + + http_client.request.assert_called_once_with( + VOICE_API_ROOT + '/' + VOICE_WEB_HOOKS_PATH + '/' + webhook_id, 'GET', None) + + self.assertEqual(webhook_id, voice_webhook.id) + self.assertEqual(webhook_token, voice_webhook.token) + + def test_voice_list_webhook(self): + http_client = Mock() + http_client.request.return_value = '''{ + "data": [ + { + "id": "534e1848-235f-482d-983d-e3e11a04f58a", + "url": "https://example.com/", + "token": "foobar", + "createdAt": "2017-03-15T13:28:32Z", + "updatedAt": "2017-03-15T13:28:32Z", + "_links": { + "self": "/webhooks/534e1848-235f-482d-983d-e3e11a04f58a" + } + }, + { + "id": "123e345-235f-482d-983d-e3e11a04f58a", + "url": "https://gogol.com/", + "token": "barbar", + "createdAt": "2017-03-15T13:28:32Z", + "updatedAt": "2017-03-15T13:28:32Z", + "_links": { + "self": "/webhooks/534e1848-235f-482d-983d-e3e11a04f58a" + } + } + ], + "_links": { + "self": "/webhooks?page=1" + }, + "pagination": { + "totalCount": 1, + "pageCount": 1, + "currentPage": 1, + "perPage": 10 + } + }''' + + webhook_id = '534e1848-235f-482d-983d-e3e11a04f58a' + webhook_token = 'foobar' + + voice_webhook_list = Client('', http_client).voice_list_webhooks(limit=10, offset=0) + + http_client.request.assert_called_once_with( + VOICE_API_ROOT + '/' + VOICE_WEB_HOOKS_PATH + '?limit=10&offset=0', 'GET', None) + + webhooks = voice_webhook_list.data + self.assertEqual(2, len(webhooks)) + self.assertEqual(webhook_id, webhooks[0].id) + self.assertEqual(webhook_token, webhooks[0].token) + + def test_voice_create_webhook(self): + http_client = Mock() + http_client.request.return_value = '''{ + "data": [ + { + "id": "534e1848-235f-482d-983d-e3e11a04f58a", + "url": "https://example.com/", + "token": "foobar", + "createdAt": "2017-03-15T14:10:07Z", + "updatedAt": "2017-03-15T14:10:07Z" + } + ], + "_links": { + "self": "/webhooks/534e1848-235f-482d-983d-e3e11a04f58a" + } + }''' + + create_webhook_request = VoiceCreateWebhookRequest(url="https://example.com/", title="FooBar", token="foobar") + created_webhook = Client('', http_client).voice_create_webhook(create_webhook_request) + + http_client.request.assert_called_once_with(VOICE_API_ROOT + '/' + VOICE_WEB_HOOKS_PATH, 'POST', + create_webhook_request.__dict__()) + self.assertEqual(create_webhook_request.url, created_webhook.url) + self.assertEqual(create_webhook_request.token, created_webhook.token) + + def test_voice_create_webhook_request_validation(self): + url = "https://example.com/" + title = "FooBar" + token = "FooBarToken" + blank_string = ' ' + + with self.assertRaises(ValidationError): + VoiceCreateWebhookRequest(title=title) + + request = VoiceCreateWebhookRequest(url=url, title=title) + + self.assertEqual(url, request.url) + self.assertEqual(title, request.title) + self.assertEqual(None, request.token) + + request.url = url + url + with self.assertRaises(ValidationError): + request.url = blank_string + + def test_voice_update_webhook(self): + http_client = Mock() + http_client.request.return_value = '''{ + "data": [ + { + "id": "534e1848-235f-482d-983d-e3e11a04f58a", + "url": "https://example.com/baz", + "token": "foobar", + "createdAt": "2017-03-15T13:27:02Z", + "updatedAt": "2017-03-15T13:28:01Z" + } + ], + "_links": { + "self": "/webhooks/534e1848-235f-482d-983d-e3e11a04f58a" + } + }''' + webhook_id = '534e1848-235f-482d-983d-e3e11a04f58a' + update_webhook_request = VoiceUpdateWebhookRequest(title="FooBar", token="foobar") + updated_webhook = Client('', http_client).voice_update_webhook(webhook_id, update_webhook_request) + + http_client.request.assert_called_once_with( + VOICE_API_ROOT + '/' + VOICE_WEB_HOOKS_PATH + '/' + webhook_id, 'PUT', update_webhook_request.__dict__()) + + self.assertEqual(update_webhook_request.token, updated_webhook.token) + + def test_voice_delete_webhook(self): + http_client = Mock() + http_client.request.return_value = '' + webhook_id = '534e1848-235f-482d-983d-e3e11a04f58a' + Client('', http_client).voice_delete_webhook(webhook_id) + + http_client.request.assert_called_once_with( + VOICE_API_ROOT + '/' + VOICE_WEB_HOOKS_PATH + '/' + webhook_id, 'DELETE', None) + + def test_check_serialization(self): + json_serialize(VoiceCreateWebhookRequest(url="https://someurl.com", title="foobar")) + json_serialize(VoiceUpdateWebhookRequest(title="foobar")) diff --git a/tests/test_voicemessage.py b/tests/test_voicemessage.py index c7f2a14..6130843 100644 --- a/tests/test_voicemessage.py +++ b/tests/test_voicemessage.py @@ -25,6 +25,14 @@ def test_voicemessage_create(self): http_client = Mock() http_client.request.return_value = '{}' - Client('', http_client).voice_message_create(['31612345678', '31687654321'], 'Hello World', { 'reference': 'MyReference' }) - - http_client.request.assert_called_once_with('voicemessages', 'POST', {'body': 'Hello World', 'recipients': '31612345678,31687654321', 'reference': 'MyReference'}) + Client('', http_client).voice_message_create( + ['31612345678', '31687654321'], + 'Hello World', + {'reference': 'MyReference'} + ) + + http_client.request.assert_called_once_with( + 'voicemessages', 'POST', + {'body': 'Hello World', 'recipients': '31612345678,31687654321', + 'reference': 'MyReference'} + )