Skip to content
This repository has been archived by the owner on Jun 12, 2018. It is now read-only.

Commit

Permalink
Merge branch 'develop' into feature/issue-1133-add-an-account-field-f…
Browse files Browse the repository at this point in the history
…or-account-types
  • Loading branch information
justinvdm committed Jan 6, 2015
2 parents f402ccf + a9913ab commit 11cfc77
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 32 deletions.
162 changes: 162 additions & 0 deletions go/vumitools/opt_out/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
from twisted.internet.defer import inlineCallbacks

from vumi.tests.helpers import VumiTestCase

from go.vumitools.utils import MessageMetadataHelper
from go.vumitools.tests.helpers import VumiApiHelper, GoMessageHelper
from go.vumitools.opt_out.utils import OptOutHelper


class TestOptOutHelper(VumiTestCase):
@inlineCallbacks
def setUp(self):
self.vumi_helper = yield self.add_helper(VumiApiHelper())
self.user_helper = yield self.vumi_helper.make_user(u'testuser')
self.vumi_api = self.vumi_helper.get_vumi_api()
self.account = yield self.user_helper.get_user_account()
self.msg_helper = self.add_helper(GoMessageHelper())

@inlineCallbacks
def test_process_message_opt_out(self):
"""
If the message input matches an opt out keyword, it should add the
appropriate opt out metadata to the message to mark it as an opt out.
"""
optouts = OptOutHelper(self.vumi_api, {'keywords': ['stop', 'halt']})

msg = self.msg_helper.make_inbound('stop')
yield optouts.process_message(self.account, msg)

self.assertEqual(msg['helper_metadata']['optout'], {
'optout': True,
'optout_keyword': 'stop',
})

msg = self.msg_helper.make_inbound('halt')
yield optouts.process_message(self.account, msg)

self.assertEqual(msg['helper_metadata']['optout'], {
'optout': True,
'optout_keyword': 'halt',
})

@inlineCallbacks
def test_process_message_non_opt_out(self):
"""
If the message input does not match an opt out keyword, it should add
the appropriate opt out metadata to the message to mark it as not an
opt out.
"""
optouts = OptOutHelper(self.vumi_api, {'keywords': ['stop', 'halt']})

msg = self.msg_helper.make_inbound('hi')
yield optouts.process_message(self.account, msg)
self.assertEqual(msg['helper_metadata']['optout'], {'optout': False})

@inlineCallbacks
def test_process_message_case_insensitive(self):
"""
If the helper is configured to be case insensitive, should match
message input to opt out keywords regardless of the casing.
"""
optouts = OptOutHelper(self.vumi_api, {
'case_sensitive': False,
'keywords': ['STOP']
})

msg = self.msg_helper.make_inbound('stop')
yield optouts.process_message(self.account, msg)

self.assertEqual(msg['helper_metadata']['optout'], {
'optout': True,
'optout_keyword': 'stop',
})

msg = self.msg_helper.make_inbound('sToP')
yield optouts.process_message(self.account, msg)

self.assertEqual(msg['helper_metadata']['optout'], {
'optout': True,
'optout_keyword': 'stop',
})

msg = self.msg_helper.make_inbound('STOP')
yield optouts.process_message(self.account, msg)

self.assertEqual(msg['helper_metadata']['optout'], {
'optout': True,
'optout_keyword': 'stop',
})

@inlineCallbacks
def test_process_message_case_sensitive(self):
"""
If the helper is configured to be case sensitive, should only match
message input to opt out keywords if their casing matches.
"""
optouts = OptOutHelper(self.vumi_api, {
'case_sensitive': True,
'keywords': ['STOP']
})

msg = self.msg_helper.make_inbound('stop')
yield optouts.process_message(self.account, msg)
self.assertEqual(msg['helper_metadata']['optout'], {'optout': False})

msg = self.msg_helper.make_inbound('sToP')
yield optouts.process_message(self.account, msg)
self.assertEqual(msg['helper_metadata']['optout'], {'optout': False})

msg = self.msg_helper.make_inbound('STOP')
yield optouts.process_message(self.account, msg)

self.assertEqual(msg['helper_metadata']['optout'], {
'optout': True,
'optout_keyword': 'STOP',
})

@inlineCallbacks
def test_process_message_disabled_by_tagpool(self):
"""
If the message is being sent using a tag in a tagpool with global opt
outs disabled, it should process the message as a non-opt out.
"""
optouts = OptOutHelper(self.vumi_api, {'keywords': ['stop']})

yield self.vumi_helper.setup_tagpool(u'pool1', [u'tag1'], {
'disable_global_opt_out': True
})

msg = self.msg_helper.make_inbound('stop')
md = MessageMetadataHelper(self.vumi_api, msg)
md.set_tag((u'pool1', u'tag1'))

yield optouts.process_message(self.account, msg)
self.assertEqual(msg['helper_metadata']['optout'], {'optout': False})

@inlineCallbacks
def test_process_message_already_processed(self):
"""
If the message has already been processed as an opt out or non-opt out,
it should not be reprocessed.
"""
optouts = OptOutHelper(self.vumi_api, {'keywords': ['stop']})

msg = self.msg_helper.make_inbound('stop')
msg['helper_metadata']['optout'] = {'optout': False}
yield optouts.process_message(self.account, msg)
self.assertEqual(msg['helper_metadata']['optout'], {'optout': False})

def test_is_optout_message(self):
"""
It should return True if the message contains the relevant metadata for
it to count as an opt out, otherwise False.
"""
msg = self.msg_helper.make_inbound('hi')
self.assertFalse(OptOutHelper.is_optout_message(msg))

msg['helper_metadata'] = {'optout': {'optout': True}}
self.assertTrue(OptOutHelper.is_optout_message(msg))

msg['helper_metadata'] = {'optout': {'optout': False}}
self.assertFalse(OptOutHelper.is_optout_message(msg))
68 changes: 68 additions & 0 deletions go/vumitools/opt_out/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from twisted.internet.defer import inlineCallbacks, returnValue

from vumi.config import Config, ConfigBool, ConfigList

from go.vumitools.utils import MessageMetadataHelper


class OptOutHelperConfig(Config):
case_sensitive = ConfigBool(
"Whether case sensitivity should be enforced when checking message "
"content for opt outs",
default=False)

keywords = ConfigList(
"List of the keywords which count as opt outs",
default=())


class OptOutHelper(object):
def __init__(self, vumi_api, config):
self.vumi_api = vumi_api
self.config = OptOutHelperConfig(config)
self.optout_keywords = set([
self.casing(word) for word in self.config.keywords])

def casing(self, word):
if not self.config.case_sensitive:
return word.lower()
return word

def keyword(self, message):
keyword = (message['content'] or '').strip()
return self.casing(keyword)

@inlineCallbacks
def _optout_disabled(self, account, message):
msg_mdh = MessageMetadataHelper(self.vumi_api, message)

if msg_mdh.tag is not None:
tagpool_metadata = yield msg_mdh.get_tagpool_metadata()
returnValue(tagpool_metadata.get('disable_global_opt_out', False))
else:
returnValue(False)

@inlineCallbacks
def _is_optout(self, account, message):
if (yield self._optout_disabled(account, message)):
returnValue(False)
else:
returnValue(self.keyword(message) in self.optout_keywords)

@inlineCallbacks
def process_message(self, account, message):
helper_metadata = message['helper_metadata']

if 'optout' not in helper_metadata:
optout_metadata = {'optout': False}
helper_metadata['optout'] = optout_metadata

if (yield self._is_optout(account, message)):
optout_metadata['optout'] = True
optout_metadata['optout_keyword'] = self.keyword(message)

returnValue(message)

@staticmethod
def is_optout_message(message):
return message['helper_metadata'].get('optout', {}).get('optout')
11 changes: 11 additions & 0 deletions go/vumitools/routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from go.vumitools.app_worker import GoWorkerMixin, GoWorkerConfigMixin
from go.vumitools.routing_table import GoConnector
from go.vumitools.opt_out.utils import OptOutHelper


class RoutingError(Exception):
Expand Down Expand Up @@ -292,6 +293,9 @@ class AccountRoutingTableDispatcherConfig(RoutingTableDispatcher.CONFIG_CLASS,
"If true (the default), outbound messages to transports will be"
" written to the message store.",
static=True, default=True)
optouts = ConfigDict(
"Configuration options for the opt out helper",
static=True, default={})


class AccountRoutingTableDispatcher(RoutingTableDispatcher, GoWorkerMixin):
Expand Down Expand Up @@ -401,6 +405,8 @@ def setup_dispatcher(self):
self.transport_connectors -= self.router_connectors
self.transport_connectors -= self.billing_connectors

self.optouts = OptOutHelper(self.vumi_api, config.optouts)

@inlineCallbacks
def teardown_dispatcher(self):
yield self.account_cache.cleanup()
Expand Down Expand Up @@ -773,6 +779,11 @@ def process_inbound(self, config, msg, connector_name):
* the billing worker
"""
log.debug("Processing inbound: %r" % (msg,))

user_api = self.get_user_api(config.user_account_key)
account = yield self.account_cache.get_account(user_api)
yield self.optouts.process_message(account, msg)

msg_mdh = self.get_metadata_helper(msg)
msg_mdh.set_user_account(config.user_account_key)

Expand Down
Loading

0 comments on commit 11cfc77

Please sign in to comment.