Skip to content

Commit

Permalink
Merge branch 'develop' into feature/issue-623-smpp-sar-concatenated-m…
Browse files Browse the repository at this point in the history
…essages
  • Loading branch information
jerith committed Oct 21, 2013
2 parents f56480e + 8f3bfa0 commit 2cc2a35
Show file tree
Hide file tree
Showing 7 changed files with 349 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ language: python
python:
- "2.6"
- "2.7"
node_js:
- "0.10"
services:
- riak
- redis-server
Expand Down
33 changes: 31 additions & 2 deletions vumi/scripts/tests/test_vumi_tagpools.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from pkg_resources import resource_filename

from twisted.trial.unittest import TestCase
from twisted.python import usage

from vumi.tests.utils import PersistenceMixin

Expand Down Expand Up @@ -71,7 +70,7 @@ def test_create_pool_explicit_tags(self):


class UpdatePoolMetadataCmdTestCase(TagPoolBaseTestCase):
def test_create_pool_range_tags(self):
def test_update_tagpool_metadata(self):
cfg = make_cfg(["update-pool-metadata", "shortcode"])
cfg.run()
self.assertEqual(cfg.output, [
Expand All @@ -82,6 +81,36 @@ def test_create_pool_range_tags(self):
{'transport_type': 'sms'})


class UpdateAllPoolMetadataCmdTestCase(TagPoolBaseTestCase):
def test_update_all_metadata(self):
cfg = make_cfg(["update-all-metadata"])
cfg.tagpool.declare_tags([("xmpp", "tag"), ("longcode", "tag")])
cfg.run()
self.assertEqual(cfg.output, [
'Updating pool metadata.',
'Note: Pools not present in both the config and tagpool'
' store will not be updated.',
' Updating metadata for pool longcode ...',
' Updating metadata for pool xmpp ...',
'Done.'
])
self.assertEqual(cfg.tagpool.get_metadata("longcode"),
{u'transport_type': u'sms'})
self.assertEqual(cfg.tagpool.get_metadata("xmpp"),
{u'transport_type': u'xmpp'})
self.assertEqual(cfg.tagpool.get_metadata("shortcode"), {})

def test_no_pools(self):
cfg = make_cfg(["update-all-metadata"])
cfg.run()
self.assertEqual(cfg.output, [
'Updating pool metadata.',
'Note: Pools not present in both the config and tagpool'
' store will not be updated.',
'No pools found.',
])


class PurgePoolCmdTestCase(TagPoolBaseTestCase):
def test_purge_pool(self):
cfg = make_cfg(["purge-pool", "foo"])
Expand Down
24 changes: 24 additions & 0 deletions vumi/scripts/vumi_tagpools.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,28 @@ def run(self, cfg):
cfg.emit(" Done.")


class UpdateAllPoolMetadataCmd(usage.Options):
def run(self, cfg):
pools_in_tagpool = cfg.tagpool.list_pools()
pools_in_cfg = set(cfg.pools.keys())
pools_in_both = sorted(pools_in_tagpool.intersection(pools_in_cfg))

cfg.emit("Updating pool metadata.")
cfg.emit("Note: Pools not present in both the config and tagpool"
" store will not be updated.")

if not pools_in_both:
cfg.emit("No pools found.")
return

for pool in pools_in_both:
cfg.emit(" Updating metadata for pool %s ..." % pool)
metadata = cfg.metadata(pool)
cfg.tagpool.set_metadata(pool, metadata)

cfg.emit("Done.")


class PurgePoolCmd(PoolSubCmd):
def run(self, cfg):
cfg.emit("Purging pool %s ..." % self.pool)
Expand Down Expand Up @@ -144,6 +166,8 @@ class Options(usage.Options):
"Declare tags for a tag pool."],
["update-pool-metadata", None, UpdatePoolMetadataCmd,
"Update a pool's metadata from config."],
["update-all-metadata", None, UpdateAllPoolMetadataCmd,
"Update all pool meta data from config."],
["purge-pool", None, PurgePoolCmd,
"Purge all tags from a tag pool."],
["list-keys", None, ListKeysCmd,
Expand Down
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']
88 changes: 88 additions & 0 deletions vumi/transports/mtech_kenya/mtech_kenya.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# -*- 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"])
OPTIONAL_FIELDS = set(["linkID", "gateway", "message_type"])

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'],
}
link_id = message['transport_metadata'].get('linkID')
if link_id is not None:
params['linkID'] = link_id
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.OPTIONAL_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)
transport_metadata = {'transport_message_id': values['messageID']}
if values.get('linkID') is not None:
transport_metadata['linkID'] = values['linkID']
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_metadata,
)
yield self.finish_request(
message_id, json.dumps({'message_id': message_id}))
Empty file.
201 changes: 201 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,201 @@
# -*- 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'])

@inlineCallbacks
def test_inbound_linkid(self):
url = self.mkurl('hello', linkID='link123')
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(msg['transport_metadata'], {
'transport_message_id': '1234567',
'linkID': 'link123',
})
self.assertEqual(json.loads(response),
{'message_id': msg['message_id']})

@inlineCallbacks
def test_outbound_linkid(self):
msg = self.mkmsg_out(to_addr="2371234567", transport_metadata={
'linkID': 'link123',
})
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'],
'linkID': ['link123'],
}, req.args)
[ack] = yield self.wait_for_dispatched_events(1)
self.assertEqual('ack', ack['event_type'])

0 comments on commit 2cc2a35

Please sign in to comment.