From e00159ba4bb52a383367b74acd96bb50a93e4e49 Mon Sep 17 00:00:00 2001 From: justinvdm Date: Fri, 13 Feb 2015 16:59:45 +0200 Subject: [PATCH] Send session lengths from billing dispatcher to billing api --- go/vumitools/billing_worker.py | 41 ++++- go/vumitools/tests/test_billing_worker.py | 181 +++++++++++++++++++++- 2 files changed, 216 insertions(+), 6 deletions(-) diff --git a/go/vumitools/billing_worker.py b/go/vumitools/billing_worker.py index d2ebca857..4de108ae1 100644 --- a/go/vumitools/billing_worker.py +++ b/go/vumitools/billing_worker.py @@ -64,7 +64,7 @@ def _call_api(self, path, query=None, data=None, method='GET'): def create_transaction(self, account_number, message_id, tag_pool_name, tag_name, provider, message_direction, - session_created, transaction_type): + session_created, transaction_type, session_length): """Create a new transaction for the given ``account_number``""" data = { 'account_number': account_number, @@ -75,6 +75,7 @@ def create_transaction(self, account_number, message_id, tag_pool_name, 'message_direction': message_direction, 'session_created': session_created, 'transaction_type': transaction_type, + 'session_length': session_length, } return self._call_api("/transactions", data=data, method='POST') @@ -90,6 +91,10 @@ class BillingDispatcherConfig(Dispatcher.CONFIG_CLASS, GoWorkerConfigMixin): disable_billing = ConfigBool( "Disable calling the billing API and just pass through all messages.", static=True, default=False) + session_metadata_field = ConfigText( + "Name of the session metadata field to look for in each message to " + "calculate session length", + static=True, default='session_metadata') def post_validate(self): if len(self.receive_inbound_connectors) != 1: @@ -127,6 +132,7 @@ def setup_dispatcher(self): self.api_url = config.api_url self.billing_api = BillingApi(self.api_url, config.retry_delay) self.disable_billing = config.disable_billing + self.session_metadata_field = config.session_metadata_field @inlineCallbacks def teardown_dispatcher(self): @@ -144,6 +150,33 @@ def validate_metadata(self, msg): raise BillingError( "No tag found for message %s" % (msg.get('message_id'),)) + @classmethod + def determine_session_length(cls, session_metadata_field, msg): + """ + Determines the length of the session from metadata attached to the + message. The billing dispatcher looks for the following on the message + payload to calculate this: + + - ``helper_metadata..session_start`` + - ``helper_metadata..session_end`` + + If either of these fields are not present, the message is assumed to not + contain enough information to calculate the session length and ``None`` + is returned + """ + metadata = msg['helper_metadata'].get(session_metadata_field, {}) + + if 'session_start' not in metadata: + return None + + if 'session_end' not in metadata: + return None + + return metadata['session_end'] - metadata['session_start'] + + def _determine_session_length(self, msg): + return self.determine_session_length(self.session_metadata_field, msg) + @inlineCallbacks def create_transaction_for_inbound(self, msg): """Create a transaction for the given inbound message""" @@ -157,7 +190,8 @@ def create_transaction_for_inbound(self, msg): provider=msg.get('provider'), message_direction=self.MESSAGE_DIRECTION_INBOUND, session_created=session_created, - transaction_type=self.TRANSACTION_TYPE_MESSAGE) + transaction_type=self.TRANSACTION_TYPE_MESSAGE, + session_length=self._determine_session_length(msg)) @inlineCallbacks def create_transaction_for_outbound(self, msg): @@ -172,7 +206,8 @@ def create_transaction_for_outbound(self, msg): provider=msg.get('provider'), message_direction=self.MESSAGE_DIRECTION_OUTBOUND, session_created=session_created, - transaction_type=self.TRANSACTION_TYPE_MESSAGE) + transaction_type=self.TRANSACTION_TYPE_MESSAGE, + session_length=self._determine_session_length(msg)) @inlineCallbacks def process_inbound(self, config, msg, connector_name): diff --git a/go/vumitools/tests/test_billing_worker.py b/go/vumitools/tests/test_billing_worker.py index 8b2f509a4..af5f7dfa3 100644 --- a/go/vumitools/tests/test_billing_worker.py +++ b/go/vumitools/tests/test_billing_worker.py @@ -29,7 +29,7 @@ def _record(self, items, vars): def create_transaction(self, account_number, message_id, tag_pool_name, tag_name, provider, message_direction, - session_created, transaction_type): + session_created, transaction_type, session_length): self._record(self.transactions, locals()) return { "id": 1, @@ -48,7 +48,8 @@ def create_transaction(self, account_number, message_id, tag_pool_name, "created": "2013-10-30T10:42:51.144745+02:00", "last_modified": "2013-10-30T10:42:51.144745+02:00", "status": "Completed", - "transaction_type": transaction_type + "transaction_type": transaction_type, + "session_length": session_length } @@ -110,6 +111,7 @@ def test_create_transaction_request(self): 'message_direction': "Inbound", 'session_created': False, 'transaction_type': BillingDispatcher.TRANSACTION_TYPE_MESSAGE, + 'session_length': 23, } yield self.billing_api.create_transaction(**kwargs) self.assertEqual(hrm.request.uri, "%stransactions" % (self.api_url,)) @@ -135,6 +137,7 @@ def test_create_transaction_response(self): "last_modified": "2013-10-30T10:42:51.144745+02:00", "status": "Completed", "transaction_type": BillingDispatcher.TRANSACTION_TYPE_MESSAGE, + 'session_length': 23, } response = self._mk_response( delivered_body=json.dumps(delivered_body, cls=JSONEncoder)) @@ -152,6 +155,7 @@ def test_create_transaction_response(self): 'message_direction': "Inbound", 'session_created': False, "transaction_type": BillingDispatcher.TRANSACTION_TYPE_MESSAGE, + 'session_length': 23, } result = yield self.billing_api.create_transaction(**kwargs) self.assertEqual(result, delivered_body) @@ -174,6 +178,7 @@ def test_create_transaction_http_error(self): 'message_direction': "Inbound", 'session_created': False, 'transaction_type': BillingDispatcher.TRANSACTION_TYPE_MESSAGE, + 'session_length': 23, } d = self.billing_api.create_transaction(**kwargs) yield self.assertFailure(d, BillingError) @@ -197,6 +202,7 @@ def test_create_transaction_network_error(self): "last_modified": "2013-10-30T10:42:51.144745+02:00", "status": "Completed", "transaction_type": BillingDispatcher.TRANSACTION_TYPE_MESSAGE, + 'session_length': 23, } response = self._mk_response( delivered_body=json.dumps(delivered_body, cls=JSONEncoder)) @@ -214,6 +220,7 @@ def test_create_transaction_network_error(self): 'message_direction': "Inbound", 'session_created': False, "transaction_type": BillingDispatcher.TRANSACTION_TYPE_MESSAGE, + 'session_length': 23, } result = yield self.billing_api.create_transaction(**kwargs) self.assertEqual(result, delivered_body) @@ -236,6 +243,7 @@ def test_create_transaction_network_error_on_retry(self): 'message_direction': "Inbound", 'session_created': False, "transaction_type": BillingDispatcher.TRANSACTION_TYPE_MESSAGE, + 'session_length': 23, } d = self.billing_api.create_transaction(**kwargs) yield self.assertFailure(d, MockNetworkError) @@ -291,12 +299,14 @@ def make_dispatch_outbound(self, content, user_account=None, tag=None, self.add_md(msg, user_account=user_account, tag=tag, is_paid=is_paid) return self.ro_helper.dispatch_outbound(msg).addCallback(lambda _: msg) - def assert_transaction(self, msg, direction, session_created): + def assert_transaction(self, msg, direction, session_created, + session_metadata_field='session_metadata'): md = MessageMetadataHelper(self.vumi_helper.get_vumi_api(), msg) direction = { "inbound": BillingDispatcher.MESSAGE_DIRECTION_INBOUND, "outbound": BillingDispatcher.MESSAGE_DIRECTION_OUTBOUND, }[direction] + self.assertEqual(self.billing_api.transactions, [{ "account_number": md.get_account_key(), "message_id": msg["message_id"], @@ -306,11 +316,40 @@ def assert_transaction(self, msg, direction, session_created): "message_direction": direction, "session_created": session_created, "transaction_type": BillingDispatcher.TRANSACTION_TYPE_MESSAGE, + "session_length": BillingDispatcher.determine_session_length( + session_metadata_field, msg), }]) def assert_no_transactions(self): self.assertEqual(self.billing_api.transactions, []) + def test_determine_session_length(self): + msg = self.msg_helper.make_inbound('roar', helper_metadata={ + 'foo': { + 'session_start': 32, + 'session_end': 55, + } + }) + + self.assertEqual( + BillingDispatcher.determine_session_length('foo', msg), 23) + + def test_determine_session_length_no_start(self): + msg = self.msg_helper.make_inbound('roar', helper_metadata={ + 'foo': {'session_start': 32} + }) + + self.assertEqual( + BillingDispatcher.determine_session_length('foo', msg), None) + + def test_determine_session_length_no_end(self): + msg = self.msg_helper.make_inbound('roar', helper_metadata={ + 'foo': {'session_end': 55} + }) + + self.assertEqual( + BillingDispatcher.determine_session_length('foo', msg), None) + @inlineCallbacks def test_inbound_message(self): yield self.get_dispatcher() @@ -388,6 +427,74 @@ def test_inbound_message_provider(self): self.assertEqual([msg], self.ro_helper.get_dispatched_inbound()) self.assert_transaction(msg, "inbound", session_created=False) + @inlineCallbacks + def test_inbound_message_session_length(self): + yield self.get_dispatcher() + msg = yield self.make_dispatch_inbound( + "inbound", + user_account="12345", + tag=("pool1", "1234"), + helper_metadata={ + 'session_metadata': { + 'session_start': 32, + 'session_end': 55, + } + }) + + self.add_md(msg, is_paid=True) + self.assertEqual([msg], self.ro_helper.get_dispatched_inbound()) + self.assert_transaction(msg, "inbound", session_created=False) + + @inlineCallbacks + def test_inbound_message_session_length_no_start(self): + yield self.get_dispatcher() + msg = yield self.make_dispatch_inbound( + "inbound", + user_account="12345", + tag=("pool1", "1234"), + helper_metadata={'session_metadata': {'session_end': 55}}) + + self.add_md(msg, is_paid=True) + self.assertEqual([msg], self.ro_helper.get_dispatched_inbound()) + self.assert_transaction(msg, "inbound", session_created=False) + + @inlineCallbacks + def test_inbound_message_session_length_no_end(self): + yield self.get_dispatcher() + msg = yield self.make_dispatch_inbound( + "inbound", + user_account="12345", + tag=("pool1", "1234"), + helper_metadata={'session_metadata': {'session_start': 32}}) + + self.add_md(msg, is_paid=True) + self.assertEqual([msg], self.ro_helper.get_dispatched_inbound()) + self.assert_transaction(msg, "inbound", session_created=False) + + @inlineCallbacks + def test_inbound_message_session_length_custom_field(self): + yield self.get_dispatcher(session_metadata_field='foo') + + msg = yield self.make_dispatch_inbound( + "inbound", + user_account="12345", + tag=("pool1", "1234"), + helper_metadata={ + 'foo': { + 'session_start': 32, + 'session_end': 55, + } + }) + + self.add_md(msg, is_paid=True) + self.assertEqual([msg], self.ro_helper.get_dispatched_inbound()) + + self.assert_transaction( + msg, + "inbound", + session_created=False, + session_metadata_field='foo') + @inlineCallbacks def test_outbound_message(self): yield self.get_dispatcher() @@ -564,3 +671,71 @@ def create_transaction(*args, **kw): self.assertEqual( [err.getErrorMessage() for err in errors], ["I can't do that, Dave."]) + + @inlineCallbacks + def test_outbound_message_session_length(self): + yield self.get_dispatcher() + msg = yield self.make_dispatch_outbound( + "outbound", + user_account="12345", + tag=("pool1", "1234"), + helper_metadata={ + 'session_metadata': { + 'session_start': 32, + 'session_end': 55, + } + }) + + self.add_md(msg, is_paid=True) + self.assertEqual([msg], self.ri_helper.get_dispatched_outbound()) + self.assert_transaction(msg, "outbound", session_created=False) + + @inlineCallbacks + def test_outbound_message_session_length_no_start(self): + yield self.get_dispatcher() + msg = yield self.make_dispatch_outbound( + "outbound", + user_account="12345", + tag=("pool1", "1234"), + helper_metadata={'session_metadata': {'session_end': 55}}) + + self.add_md(msg, is_paid=True) + self.assertEqual([msg], self.ri_helper.get_dispatched_outbound()) + self.assert_transaction(msg, "outbound", session_created=False) + + @inlineCallbacks + def test_outbound_message_session_length_no_end(self): + yield self.get_dispatcher() + msg = yield self.make_dispatch_outbound( + "outbound", + user_account="12345", + tag=("pool1", "1234"), + helper_metadata={'session_metadata': {'session_start': 32}}) + + self.add_md(msg, is_paid=True) + self.assertEqual([msg], self.ri_helper.get_dispatched_outbound()) + self.assert_transaction(msg, "outbound", session_created=False) + + @inlineCallbacks + def test_outbound_message_session_length_custom_field(self): + yield self.get_dispatcher(session_metadata_field='foo') + + msg = yield self.make_dispatch_outbound( + "outbound", + user_account="12345", + tag=("pool1", "1234"), + helper_metadata={ + 'foo': { + 'session_start': 32, + 'session_end': 55, + } + }) + + self.add_md(msg, is_paid=True) + self.assertEqual([msg], self.ri_helper.get_dispatched_outbound()) + + self.assert_transaction( + msg, + "outbound", + session_created=False, + session_metadata_field='foo')