Skip to content

Commit

Permalink
MTech Kenya SMS transport, sans delivery report handling.
Browse files Browse the repository at this point in the history
  • Loading branch information
jerith committed Oct 15, 2013
1 parent 14ab507 commit 78cb28d
Show file tree
Hide file tree
Showing 4 changed files with 260 additions and 0 deletions.
3 changes: 3 additions & 0 deletions vumi/transports/mtech_kenya/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .mtech_kenya import MTechKenyaTransport

__all__ = ['MTechKenyaTransport']
93 changes: 93 additions & 0 deletions vumi/transports/mtech_kenya/mtech_kenya.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# -*- test-case-name: vumi.transports.mtech_kenya.tests.test_mtech_kenya -*-

import json
from urllib import urlencode

from twisted.internet.defer import inlineCallbacks

from vumi.utils import http_request_full
from vumi import log
from vumi.config import ConfigText
from vumi.transports.httprpc import HttpRpcTransport


class MTechKenyaTransportConfig(HttpRpcTransport.CONFIG_CLASS):
outbound_url = ConfigText('The URL to send outbound messages to.',
required=True, static=True)
mt_username = ConfigText('The username sent with outbound messages',
required=True, static=True)
mt_password = ConfigText('The password sent with outbound messages',
required=True, static=True)


class MTechKenyaTransport(HttpRpcTransport):
"""
HTTP transport for Cellulant SMS.
"""

transport_type = 'sms'

CONFIG_CLASS = MTechKenyaTransportConfig

EXPECTED_FIELDS = set(["shortCode", "MSISDN", "MESSAGE", "messageID"])
IGNORED_FIELDS = set()

KNOWN_ERROR_RESPONSE_CODES = {
401: 'Invalid username or password',
403: 'Invalid mobile number',
}

def validate_config(self):
config = self.get_static_config()
self._credentials = {
'user': config.mt_username,
'pass': config.mt_password,
}
self._outbound_url = config.outbound_url
return super(MTechKenyaTransport, self).validate_config()

@inlineCallbacks
def handle_outbound_message(self, message):
config = self.get_static_config()
params = {
'user': config.mt_username,
'pass': config.mt_password,
'messageID': message['message_id'],
'shortCode': message['from_addr'],
'MSISDN': message['to_addr'],
'MESSAGE': message['content'],
}
log.msg("Sending outbound message: %s" % (message,))
url = '%s?%s' % (self._outbound_url, urlencode(params))
log.msg("Making HTTP request: %s" % (url,))
response = yield http_request_full(url, '', method='POST')
log.msg("Response: (%s) %r" % (response.code, response.delivered_body))
if response.code == 200:
yield self.publish_ack(user_message_id=message['message_id'],
sent_message_id=message['message_id'])
else:
error = self.KNOWN_ERROR_RESPONSE_CODES.get(
response.code, 'Unknown response code: %s' % (response.code,))
yield self.publish_nack(message['message_id'], error)

@inlineCallbacks
def handle_raw_inbound_message(self, message_id, request):
values, errors = self.get_field_values(
request, self.EXPECTED_FIELDS, self.IGNORED_FIELDS)
if errors:
log.msg('Unhappy incoming message: %s' % (errors,))
yield self.finish_request(message_id, json.dumps(errors), code=400)
return
log.msg(('MTechKenyaTransport sending from %(MSISDN)s to '
'%(shortCode)s message "%(MESSAGE)s"') % values)
yield self.publish_message(
message_id=message_id,
content=values['MESSAGE'],
to_addr=values['shortCode'],
from_addr=values['MSISDN'],
provider='vumi',
transport_type=self.transport_type,
transport_metadata={'transport_message_id': values['messageID']},
)
yield self.finish_request(
message_id, json.dumps({'message_id': message_id}))
Empty file.
164 changes: 164 additions & 0 deletions vumi/transports/mtech_kenya/tests/test_mtech_kenya.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# -*- encoding: utf-8 -*-

import json
from urllib import urlencode

from twisted.internet.defer import inlineCallbacks, DeferredQueue

from vumi.utils import http_request, http_request_full
from vumi.tests.utils import MockHttpServer
from vumi.transports.tests.utils import TransportTestCase
from vumi.transports.mtech_kenya import MTechKenyaTransport


class TestMTechKenyaTransport(TransportTestCase):

transport_name = 'test_mtech_kenya_transport'
transport_class = MTechKenyaTransport

@inlineCallbacks
def setUp(self):
super(TestMTechKenyaTransport, self).setUp()

self.cellulant_sms_calls = DeferredQueue()
self.mock_mtech_sms = MockHttpServer(self.handle_request)
yield self.mock_mtech_sms.start()

self.valid_creds = {
'mt_username': 'testuser',
'mt_password': 'testpass',
}
self.config = {
'transport_name': self.transport_name,
'web_path': "foo",
'web_port': 0,
'outbound_url': self.mock_mtech_sms.url,
}
self.config.update(self.valid_creds)
self.transport = yield self.get_transport(self.config)
self.transport_url = self.transport.get_transport_url()

@inlineCallbacks
def tearDown(self):
yield self.mock_mtech_sms.stop()
yield super(TestMTechKenyaTransport, self).tearDown()

def handle_request(self, request):
if request.args.get('user') != [self.valid_creds['mt_username']]:
request.setResponseCode(401)
elif request.args.get('MSISDN') != ['2371234567']:
request.setResponseCode(403)
self.cellulant_sms_calls.put(request)
return ''

def mkurl(self, content, from_addr="2371234567", **kw):
params = {
'shortCode': '12345',
'MSISDN': from_addr,
'MESSAGE': content,
'messageID': '1234567',
}
params.update(kw)
return self.mkurl_raw(**params)

def mkurl_raw(self, **params):
return '%s%s?%s' % (
self.transport_url,
self.config['web_path'],
urlencode(params)
)

@inlineCallbacks
def test_health(self):
result = yield http_request(
self.transport_url + "health", "", method='GET')
self.assertEqual(json.loads(result), {'pending_requests': 0})

@inlineCallbacks
def test_inbound(self):
url = self.mkurl('hello')
response = yield http_request(url, '', method='POST')
[msg] = self.get_dispatched_messages()
self.assertEqual(msg['transport_name'], self.transport_name)
self.assertEqual(msg['to_addr'], "12345")
self.assertEqual(msg['from_addr'], "2371234567")
self.assertEqual(msg['content'], "hello")
self.assertEqual(json.loads(response),
{'message_id': msg['message_id']})

@inlineCallbacks
def test_handle_non_ascii_input(self):
url = self.mkurl(u"öæł".encode("utf-8"))
response = yield http_request(url, '', method='POST')
[msg] = self.get_dispatched_messages()
self.assertEqual(msg['transport_name'], self.transport_name)
self.assertEqual(msg['to_addr'], "12345")
self.assertEqual(msg['from_addr'], "2371234567")
self.assertEqual(msg['content'], u"öæł")
self.assertEqual(json.loads(response),
{'message_id': msg['message_id']})

@inlineCallbacks
def test_bad_parameter(self):
url = self.mkurl('hello', foo='bar')
response = yield http_request_full(url, '', method='POST')
self.assertEqual(400, response.code)
self.assertEqual(json.loads(response.delivered_body),
{'unexpected_parameter': ['foo']})

@inlineCallbacks
def test_outbound(self):
msg = self.mkmsg_out(to_addr="2371234567")
yield self.dispatch(msg)
req = yield self.cellulant_sms_calls.get()
self.assertEqual(req.path, '/')
self.assertEqual(req.method, 'POST')
self.assertEqual({
'user': ['testuser'],
'pass': ['testpass'],
'messageID': [msg['message_id']],
'shortCode': ['9292'],
'MSISDN': ['2371234567'],
'MESSAGE': ['hello world'],
}, req.args)
[ack] = yield self.wait_for_dispatched_events(1)
self.assertEqual('ack', ack['event_type'])

@inlineCallbacks
def test_outbound_bad_creds(self):
self.valid_creds['mt_username'] = 'other_user'
msg = self.mkmsg_out(to_addr="2371234567")
yield self.dispatch(msg)
req = yield self.cellulant_sms_calls.get()
self.assertEqual(req.path, '/')
self.assertEqual(req.method, 'POST')
self.assertEqual({
'user': ['testuser'],
'pass': ['testpass'],
'messageID': [msg['message_id']],
'shortCode': ['9292'],
'MSISDN': ['2371234567'],
'MESSAGE': ['hello world'],
}, req.args)
[nack] = yield self.wait_for_dispatched_events(1)
self.assertEqual('nack', nack['event_type'])
self.assertEqual('Invalid username or password', nack['nack_reason'])

@inlineCallbacks
def test_outbound_bad_msisdn(self):
msg = self.mkmsg_out(to_addr="4471234567")
yield self.dispatch(msg)
req = yield self.cellulant_sms_calls.get()
self.assertEqual(req.path, '/')
self.assertEqual(req.method, 'POST')
self.assertEqual({
'user': ['testuser'],
'pass': ['testpass'],
'messageID': [msg['message_id']],
'shortCode': ['9292'],
'MSISDN': ['4471234567'],
'MESSAGE': ['hello world'],
}, req.args)
[nack] = yield self.wait_for_dispatched_events(1)
self.assertEqual('nack', nack['event_type'])
self.assertEqual('Invalid mobile number', nack['nack_reason'])

0 comments on commit 78cb28d

Please sign in to comment.