Commit 2df5554b authored by Jérome Perrin's avatar Jérome Perrin

Review SMS API

Implement the remaining tasks from nexedi/erp5!173 and fix [#20170709-17309B6](https://nexedi.erp5.net/bug_module/20170709-17309B6)

Backport of nexedi/erp5!580

/reviewed-on https://lab.nexedi.com/nexedi/erp5-capago/merge_requests/16
parents 86316c84 eb6d3df4
"""Save the message id of the relative document"""
if document_relative_url:
document = context.getPortalObject().restrictedTraverse(document_relative_url)
document.edit(destination_reference=message_id_list[0],
gateway = gateway_relative_url)
document.edit(destination_reference=message_id,
gateway=gateway_relative_url)
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>message_id_list, document_relative_url=None, gateway_relative_url=None, **kw</string> </value>
<value> <string>message_id, document_relative_url=None, gateway_relative_url=None, **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
......
......@@ -4,42 +4,29 @@
"""
#Get recipients
if not to_url:
recipient_phone_list = [person.getDefaultMobileTelephoneValue() for person in context.getDestinationValueList()]
if None in recipient_phone_list:
recipient_phone_list = [
person.getDefaultMobileTelephoneValue() for person in context.getDestinationValueList()]
if None in recipient_phone_list:
raise ValueError("All recipients should have a default mobile phone")
to_url = [phone.asURL() for phone in recipient_phone_list]
if None in to_url:
to_url = [phone.asURL() for phone in recipient_phone_list]
if None in to_url:
raise ValueError("All recipients should have a valid default mobile phone number")
#Get sender
if not from_url:
if context.getSourceValue():
sender_phone = context.getSourceValue().getDefaultMobileTelephoneValue()
if not sender_phone:
raise ValueError("The sender(%s) should have a default mobile phone" % context.getSourceValue())
#We use title of sender
from_title = sender_phone.getTitle()
from_url = sender_phone.asURL()
if not body:
body = context.getTextContent()
body = context.getTextContent()
if not context.getStartDate():
context.setStartDate(DateTime())
context.portal_sms.activate(
for recipient in context.getDestinationList():
context.portal_sms.activate(
activity="SQLQueue",
# We do not retry these activities not to send SMS multiple times
max_retry=0,
conflict_retry=False,
).send(
).send(
text=body,
recipient=to_url,
sender=from_url,
sender_title=from_title,
message_type="MULTITEXT",
test=download,
sender=context.getSource(),
recipient=recipient,
document_relative_url=context.getRelativeUrl(),
**kw)
)
......@@ -50,7 +50,7 @@
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>from_url=None, from_title=None, to_url=None, reply_url=None, subject=None, body=None, attachment_format=None, attachment_list=None,download=False,**kw</string> </value>
<value> <string>from_url=None, from_title=None, to_url=None, reply_url=None, subject=None, body=None, attachment_format=None, attachment_list=None, download=False, **kw</string> </value>
</item>
<item>
<key> <string>_proxy_roles</string> </key>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SMS Tool" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_Transient_Objects_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Author</string>
<string>Associate</string>
<string>Assignee</string>
<string>Manager</string>
<string>Auditor</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Author</string>
<string>Associate</string>
<string>Assignee</string>
<string>Manager</string>
<string>Auditor</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Access_future_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Access_session_data_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Author</string>
<string>Associate</string>
<string>Assignee</string>
<string>Manager</string>
<string>Auditor</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Author</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_folders_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Author</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Copy_or_Move_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Author</string>
<string>Associate</string>
<string>Assignee</string>
<string>Manager</string>
<string>Auditor</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Delete_objects_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_FTP_access_Permission</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_List_folder_contents_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Author</string>
<string>Associate</string>
<string>Assignee</string>
<string>Manager</string>
<string>Auditor</string>
</tuple>
</value>
</item>
<item>
<key> <string>_List_portal_members_Permission</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Manage_properties_Permission</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Reply_to_item_Permission</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Review_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Search_ZCatalog_Permission</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Set_own_properties_Permission</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Undo_changes_Permission</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Use_mailhost_services_Permission</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_History_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Author</string>
<string>Associate</string>
<string>Assignee</string>
<string>Manager</string>
<string>Auditor</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Associate</string>
<string>Assignee</string>
<string>Manager</string>
<string>Auditor</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_management_screens_Permission</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_count</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_mt_index</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>_tree</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>portal_sms</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Length" module="BTrees.Length"/>
</pickle>
<pickle> <int>0</int> </pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
</ZopeData>
portal_sms
\ No newline at end of file
......@@ -55,50 +55,28 @@ class DummyGateway(XMLObject):
add_permission = Permissions.AddPortalContent
zope.interface.implements(interfaces.ISmsGateway)
zope.interface.implements(
interfaces.ISmsSendingGateway,
interfaces.ISmsReceivingGateway)
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Declarative properi ties
# Declarative properties
property_sheets = ( PropertySheet.Base
, PropertySheet.XMLObject
, PropertySheet.Reference
, PropertySheet.SMSGateway
)
security.declarePublic('getAllowedMessageType')
def getAllowedMessageType(self):
"""List of all message type"""
return ['text',]
security.declareProtected(Permissions.ManagePortal, 'send')
def send(self, text,recipient,sender=None, sender_title=None,
message_type="text",test=False,**kw):
"""Send a message.
Parameters:
text -- message
recipient -- phone url of destination_reference. Could be a list
sender -- phone url of source
sender_title -- Use it as source if the gateway has title mode enable
message_type -- Only 'text' is available today
test -- Force the test mode
Kw Parameters:
quality -- Quality of the SMS (default,n)
Return message id
def send(self, text, recipient, sender):
"""Send a short message.
"""
#Check messsage type
# XXX does it make sense to check message type in dummy gateway ? -jerome
#if message_type not in self.getAllowedMessageType():
# raise ValueError, "Type of message in not allowed"
#Send message (or test)
if test or self.isSimulationMode():
if self.isSimulationMode():
return None
else:
return self._generateRandomMessageId()
security.declareProtected(Permissions.ManagePortal, 'getMessageStatus')
......
......@@ -61,7 +61,9 @@ class EssendexGateway(XMLObject):
add_permission = Permissions.AddPortalContent
zope.interface.implements(interfaces.ISmsGateway)
zope.interface.implements(
interfaces.ISmsSendingGateway,
interfaces.ISmsReceivingGateway)
# Declarative security
security = ClassSecurityInfo()
......@@ -75,10 +77,6 @@ class EssendexGateway(XMLObject):
)
api_url = "https://www.esendex.com/secure/messenger/formpost"
security.declarePublic('getAllowedMessageType')
def getAllowedMessageType(self):
"""List of all message type"""
return ['text', 'binary', 'smartMessage', 'unicode']
security.declarePrivate("_fetchPageAsDict")
def _fetchPageAsDict(self,page):
......@@ -132,32 +130,17 @@ class EssendexGateway(XMLObject):
return timedelta.seconds + (timedelta.days * 24 * 60 * 60)
security.declareProtected(Permissions.ManagePortal, 'send')
def send(self, text,recipient,sender=None, sender_title=None,
message_type="text",test=False,**kw):
def send(self, text, recipient, sender):
"""Send a message.
Parameters:
text -- message
recipient -- phone url of destination_reference. Could be a list
sender -- phone url of source
sender_title -- Use it as source if the gateway has title mode enable
message_type -- Only 'text' is available today
test -- Force the test mode
Kw Parameters:
validity_period -- Validity Period of SMS (default,0)
Return message id (or list if multiple recipient)
"""
traverse = self.getPortalObject().restrictedTraverse
message_type = self.getProperty('essendex_message_type', 'text')
assert message_type in ('text', 'binary', 'smartMessage', 'unicode')
if message_type not in self.getAllowedMessageType():
raise ValueError, "Type of message in not allowed"
validity_period = self.getProperty('essendex_validity_period', 0)
validity_period = kw.get('validity_period',0)
if not isinstance(recipient, str):
recipient = ",".join([self._transformPhoneUrlToGatewayNumber(x) for x in recipient])
else:
recipient = self._transformPhoneUrlToGatewayNumber(recipient)
recipient = self._transformPhoneUrlToGatewayNumber(
traverse(recipient).getDefaultMobileTelephoneValue().asURL())
base_url = self.api_url + "/SendSMS.aspx"
params = {'Username': self.getGatewayUser(),
......@@ -170,16 +153,13 @@ class EssendexGateway(XMLObject):
'PlainText': 1,
}
if self.isTitleMode():
params['Originator'] = traverse(sender).getDefaultMobileTelephoneValue().getTitle()
else:
params['Originator'] = self._transformPhoneUrlToGatewayNumber(
traverse(sender).getDefaultMobileTelephoneValue().asURL()) or self.getDefaultSender()
if sender_title and self.isTitleMode():
params['Originator'] = sender_title
elif sender:
params['Originator'] = self._transformPhoneUrlToGatewayNumber(sender)
elif self.getDefaultSender():
params['Originator'] = self.getDefaultSender()
if test or self.isSimulationMode():
if self.isSimulationMode():
params['Test'] = 1
LOG("EssendexGateway", INFO, params)
......
......@@ -57,7 +57,9 @@ class MobytGateway(XMLObject):
add_permission = Permissions.AddPortalContent
zope.interface.implements(interfaces.ISmsGateway)
zope.interface.implements(
interfaces.ISmsSendingGateway,
interfaces.ISmsReceivingGateway)
# Declarative security
security = ClassSecurityInfo()
......@@ -72,13 +74,7 @@ class MobytGateway(XMLObject):
# see https://web.archive.org/web/20111125005954/http://www.mobyt.fr/doc/mobyt_module_http.pdf
# for documentation of this old API
api_url = "http://multilevel.mobyt.fr/sms"
security.declarePublic('getAllowedMessageType')
def getAllowedMessageType(self):
"""List of all message type"""
# `text` is here for compatibility, but the API always expected uppercase
return ['text', 'TEXT', 'MULTITEXT', 'WAPPUSH', 'UCS2', 'MULTIUCS2']
api_url = "https://multilevel.mobyt.fr/sms"
security.declarePrivate("_fetchSendResponseAsDict")
def _fetchSendResponseAsDict(self,page):
......@@ -141,41 +137,23 @@ class MobytGateway(XMLObject):
return phone
security.declareProtected(Permissions.ManagePortal, 'send')
def send(self, text,recipient,sender=None, sender_title=None,
message_type="text",test=False,**kw):
def send(self, text, recipient, sender):
"""Send a message.
Parameters:
text -- message
recipient -- phone url of destination_reference. Could be a list
sender -- phone url of source
sender_title -- Use it as source if the gateway has title mode enable
message_type -- see getAllowedMessageType
test -- Force the test mode
Kw Parameters:
quality -- Quality of the SMS (default,n)
Return message id
"""
traverse = self.getPortalObject().restrictedTraverse
#Check messsage type
if message_type not in self.getAllowedMessageType():
raise ValueError, "Type of message in not allowed"
message_type = self.getProperty('mobyt_message_type', 'MULTITEXT')
if message_type not in ('TEXT', 'MULTITEXT', 'WAPPUSH', 'UCS2', 'MULTIUCS2'):
raise ValueError("Type of message in not allowed")
#Check message qualit
quality = kw.get('quality','n') #Allow sender personalization and status of SMS
assert quality in ['n','l','ll'], "Unknown quality : '%s'" % quality
#Check message quality
quality = self.getProperty('mobyt_quality', 'n') #Allow sender personalization and status of SMS
assert quality in ('n','l','ll'), "Unknown quality : '%s'" % quality
#Recipients
if not isinstance(recipient, str):
recipient_count = len(recipient)
recipient = ",".join([self._transformPhoneUrlToGatewayNumber(x) for x in recipient])
else:
recipient = self._transformPhoneUrlToGatewayNumber(recipient)
recipient_count = 1
#Recipient
recipient = self._transformPhoneUrlToGatewayNumber(
traverse(recipient).getDefaultMobileTelephoneValue().asURL())
if recipient_count > 1:
base_url = self.api_url + "/batch.php" #Multi recipient
else:
base_url = self.api_url + "/send.php"
#Common params
......@@ -186,22 +164,19 @@ class MobytGateway(XMLObject):
"qty" : quality,
"return_id": 1}
#Define sender
if sender_title and self.isTitleMode() and quality == 'n':
params['sender'] = sender_title
elif sender:
params['sender'] = self._transformPhoneUrlToGatewayNumber(sender)
elif self.getDefaultSender():
params['sender'] = self.getDefaultSender()
if self.isTitleMode():
params['sender'] = traverse(sender).getDefaultMobileTelephoneValue().getTitle()
else:
params['sender'] = self._transformPhoneUrlToGatewayNumber(
traverse(sender).getDefaultMobileTelephoneValue().asURL()) or self.getDefaultSender()
#Define type of message
if message_type != "text":
assert quality == 'n', "This type of message require top level messsage quality"
assert message_type in self.getAllowedMessageType(), "Unknown message type"
params['operation'] = message_type
#Send message (or test)
if test or self.isSimulationMode():
if self.isSimulationMode():
LOG("MobytGateway", INFO, params)
result = {'status': "Test"}
else:
......
......@@ -50,24 +50,22 @@ class SMSTool(BaseTool):
manage_overview = DTMLFile('explainSMSTool', _dtmldir )
security.declareProtected(ManagePortal, 'send')
def send(self, text,recipient, sender=None, sender_title=None,
message_type="text", test=False, gateway_reference='default',
document_relative_url=None, activate_kw=None, **kw):
"""
def send(self, text, recipient, sender, gateway_reference='default',
document_relative_url=None, activate_kw=None):
"""Send the message
gateway_reference: send message throught the gateway with this reference.
document_relative_url (optional) : allows to send back result to a document
activate_kw (optional) : Call SMSTool_afterSend if founded in activity with
message_id_list and document_relative_url
message_id and document_relative_url
"""
gateway = self.find(gateway_reference)
gateway = self._findGateway(gateway_reference)
message_id_list = gateway.send(text=text,
message_id = gateway.send(
text=text,
recipient=recipient,
sender=sender,
sender_title=sender_title,
message_type=message_type,
test=test,
**kw)
sender=sender)
if getattr(self, 'SMSTool_afterSend'):
# We need to use activities in order to avoid any conflict
......@@ -75,31 +73,32 @@ class SMSTool(BaseTool):
if activate_kw is not None:
send_activate_kw.update(**activate_kw)
self.activate(**send_activate_kw).SMSTool_afterSend(
message_id_list,
message_id,
document_relative_url=document_relative_url,
gateway_relative_url=gateway.getRelativeUrl(),**kw)
gateway_relative_url=gateway.getRelativeUrl())
security.declareProtected(ManagePortal, 'getMessageStatus')
def getMessageStatus(self,message_id, gateway_reference='default'):
gateway = self.find(gateway_reference)
gateway = self._findGateway(gateway_reference)
return gateway.getMessageStatus(message_id)
security.declarePublic('isSendByTitleAllowed')
def isSendByTitleAllowed(self, gateway_reference='default'):
"""Define the support or not to use the title of the telephone instead of
the number when send a message."""
gateway = self.find(gateway_reference)
gateway = self._findGateway(gateway_reference)
return gateway.isTitleMode()
security.declarePublic('find')
def find(self,gateway_reference='default'):
"""Search the gateway by his reference"""
def _findGateway(self, gateway_reference='default'):
"""Search the gateway by its reference"""
result = self.searchFolder(reference=gateway_reference)
if len(result) > 0:
return result[0].getObject()
else:
raise ValueError, "Impossible to find gateway with reference %s" % gateway_reference
result = self.getPortalObject().portal_catalog.unrestrictedSearchResults(
parent_uid=self.getUid(),
reference=gateway_reference)
if result:
result, = result # ensure only one gateway with this reference
return result.getObject()
raise ValueError("Impossible to find gateway with reference %s" % gateway_reference)
......@@ -29,25 +29,33 @@
from zope.interface import Interface
class ISmsGateway(Interface):
class ISmsSendingGateway(Interface):
"""SMS Gateway allow sending Short Messages to phones.
"""
def send(text, recipient,
sender=None, sender_title=None,
message_type="text", test=False, **kw):
def send(text, recipient, sender):
"""Send a message.
TODO: write
* text: the message as an utf-8 encoded string
* recipient: relative URL of recipient person or organisation. Recipient must have a defaut mobile phone
* sender: relative URL of sender person or organisation.
TODO: is getAllowedMessageType part of this API ?
shouldn't we rely on content_type ? ( text/plain -> SMS, text/html -> MMS ? )
On most implementations, returns a message-id that can be later passed to
getMessageStatus to check the status of the message.
"""
def receive(REQUEST):
"""Public handler to push notification from the gateway"""
def getAllowedMessageType():
"""List of all allowed message type when sending a message."""
def getMessageStatus(message_id):
"""Retrieve the status of a message
Should return x in ['sent', 'delivered', 'queued', 'failed']"""
class ISmsReceivingGateway(Interface):
"""Gateway to subscribe to events fired when Short Messages are send to SMS interface.
"""
def receive(REQUEST):
"""Public handler to push notifications from the gateway
REQUEST parameters are service provider dependent.
"""
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2018 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import unittest
import os
from zope.interface.verify import verifyObject
from DateTime import DateTime
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
class ShortMessageTestCase(ERP5TypeTestCase):
def getBusinessTemplateList(self):
return ('erp5_full_text_mroonga_catalog',
'erp5_core_proxy_field_legacy',
'erp5_base',
'erp5_crm',
'erp5_short_message'
)
class TestShortMessageGateway(ShortMessageTestCase):
def _verifyGatewayPortalType(self, portal_type):
import Products.ERP5Type.interfaces
gateway = self.portal.portal_sms.newContent(portal_type=portal_type)
verifyObject(Products.ERP5Type.interfaces.ISmsSendingGateway, gateway)
verifyObject(Products.ERP5Type.interfaces.ISmsReceivingGateway, gateway)
def test_EssendexGateway(self):
self._verifyGatewayPortalType('Essendex Gateway')
def test_MobytGateway(self):
self._verifyGatewayPortalType('Mobyt Gateway')
def test_DummyGateway(self):
self._verifyGatewayPortalType('Dummy Gateway')
class TestShortMessageSending(ShortMessageTestCase):
def beforeTearDown(self):
self.abort()
self.tic()
for module in (
self.portal.portal_sms,
self.portal.person_module,
self.portal.event_module ):
module.manage_delObjects(list(module.objectIds()))
self.tic()
def test_ShortMessage_start(self):
gateway = self.portal.portal_sms.newContent(
reference='default',
portal_type='Dummy Gateway')
self.tic()
sender = self.portal.person_module.newContent(
portal_type='Person',
default_mobile_telephone_telephone_number='1234')
recipient = self.portal.person_module.newContent(
portal_type='Person',
default_mobile_telephone_telephone_number='5678')
short_message = self.portal.event_module.newContent(
portal_type="Short Message",
source_value=sender,
destination_value=recipient,
text_content='Hello')
short_message.start()
self.tic()
# sending message should have updated the document with message id and gateway
self.assertTrue(short_message.getDestinationReference())
self.assertEqual(gateway, short_message.getGatewayValue())
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestShortMessageGateway))
suite.addTest(unittest.makeSuite(TestShortMessageSending))
return suite
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment