-
Notifications
You must be signed in to change notification settings - Fork 131
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'feature/issue-626-mtech-kenya-sms-transport' into develop
- Loading branch information
Showing
4 changed files
with
249 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .mtech_kenya import MTechKenyaTransport | ||
|
||
__all__ = ['MTechKenyaTransport'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
# -*- 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', | ||
} | ||
|
||
@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'], | ||
} | ||
url = '%s?%s' % (config.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'], | ||
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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']) |