class WeChatTestCase(VumiTestCase): def setUp(self): self.tx_helper = self.add_helper(TransportHelper(WeChatTransport)) self.request_queue = DeferredQueue() self.mock_server = MockHttpServer(self.handle_api_request) self.add_cleanup(self.mock_server.stop) return self.mock_server.start() def handle_api_request(self, request): self.request_queue.put(request) return NOT_DONE_YET def get_transport(self, **config): defaults = { 'api_url': self.mock_server.url, 'auth_token': 'token', 'twisted_endpoint': 'tcp:0', 'wechat_appid': 'appid', 'wechat_secret': 'secret', 'embed_user_profile': False, } defaults.update(config) return self.tx_helper.get_transport(defaults) @inlineCallbacks def get_transport_with_access_token(self, access_token, **config): transport = yield self.get_transport(**config) yield transport.redis.set(WeChatTransport.ACCESS_TOKEN_KEY, access_token) returnValue(transport)
class TestYoPaymentHandler(VumiTestCase): @inlineCallbacks def setUp(self): self.eh_helper = self.add_helper(EventHandlerHelper()) yield self.eh_helper.setup_event_dispatcher( 'afropinions', YoPaymentHandler, { 'poll_manager_prefix': 'vumigo.', 'username': '******', 'password': '******', 'url': None, 'method': 'POST', 'amount': 1000, 'reason': 'foo', }) self.eh_helper.track_event('survey_completed', 'afropinions') @inlineCallbacks def test_hitting_url(self): msisdn = u'+2345' message_id = u'message-id' event = self.eh_helper.make_event( 'survey_completed', { 'from_addr': msisdn, 'message_id': message_id, 'transport_type': 'ussd', 'participant': { 'interactions': 2 }, }) self.mock_server = MockHttpServer() yield self.mock_server.start() self.eh_helper.get_handler('afropinions').url = self.mock_server.url yield self.eh_helper.dispatch_event(event) received_request = yield self.mock_server.queue.get() self.assertEqual(received_request.args['msisdn'][0], msisdn) self.assertEqual(received_request.args['amount'][0], '2000') self.assertEqual(received_request.args['reason'][0], 'foo') headers = received_request.requestHeaders self.assertEqual(headers.getRawHeaders('Content-Type'), ['application/x-www-form-urlencoded']) self.assertEqual( headers.getRawHeaders('Authorization'), ['Basic %s' % (base64.b64encode('username:password'), )]) yield self.mock_server.stop() def test_auth_headers(self): handler = self.eh_helper.get_handler('afropinions') auth = handler.get_auth_headers('username', 'password') credentials = base64.b64encode('username:password') self.assertEqual( auth, {'Authorization': 'Basic %s' % (credentials.strip(), )})
class TestYoPaymentHandler(VumiTestCase): @inlineCallbacks def setUp(self): self.eh_helper = self.add_helper(EventHandlerHelper()) yield self.eh_helper.setup_event_dispatcher( 'afropinions', YoPaymentHandler, { 'poll_manager_prefix': 'vumigo.', 'username': '******', 'password': '******', 'url': None, 'method': 'POST', 'amount': 1000, 'reason': 'foo', }) self.eh_helper.track_event('survey_completed', 'afropinions') @inlineCallbacks def test_hitting_url(self): msisdn = u'+2345' message_id = u'message-id' event = self.eh_helper.make_event('survey_completed', { 'from_addr': msisdn, 'message_id': message_id, 'transport_type': 'ussd', 'participant': {'interactions': 2}, }) self.mock_server = MockHttpServer() yield self.mock_server.start() self.eh_helper.get_handler('afropinions').url = self.mock_server.url yield self.eh_helper.dispatch_event(event) received_request = yield self.mock_server.queue.get() self.assertEqual(received_request.args['msisdn'][0], msisdn) self.assertEqual(received_request.args['amount'][0], '2000') self.assertEqual(received_request.args['reason'][0], 'foo') headers = received_request.requestHeaders self.assertEqual(headers.getRawHeaders('Content-Type'), ['application/x-www-form-urlencoded']) self.assertEqual(headers.getRawHeaders('Authorization'), ['Basic %s' % (base64.b64encode('username:password'),)]) yield self.mock_server.stop() def test_auth_headers(self): handler = self.eh_helper.get_handler('afropinions') auth = handler.get_auth_headers('username', 'password') credentials = base64.b64encode('username:password') self.assertEqual(auth, { 'Authorization': 'Basic %s' % (credentials.strip(),) })
class TestTransport(TestCase): @inlineCallbacks def setUp(self): DelayedCall.debug = True self.ok_transport_calls = DeferredQueue() self.mock_service = MockHttpServer(self.handle_request) yield self.mock_service.start() config = { 'transport_name': 'test_ok_transport', 'transport_type': 'ok', 'ussd_string_prefix': '', 'web_path': "foo", 'web_port': 0, 'url': self.mock_service.url, 'username': '******', 'password': '******', } self.worker = get_stubbed_worker(OkTransport, config) self.broker = self.worker._amqp_client.broker yield self.worker.startWorker() self.worker_url = self.worker.get_transport_url() @inlineCallbacks def tearDown(self): yield self.worker.stopWorker() yield self.mock_service.stop() def handle_request(self, request): self.ok_transport_calls.put(request) return '' @inlineCallbacks def test_health(self): result = yield http_request(self.worker_url + "health", "", method='GET') self.assertEqual(json.loads(result), { 'pending_requests': 0 }) @inlineCallbacks def test_inbound(self): d = http_request(self.worker_url + "foo", '', method='GET') msg, = yield self.broker.wait_messages("vumi", "test_ok_transport.inbound", 1) payload = msg.payload tum = TransportUserMessage(**payload) rep = tum.reply("OK") self.broker.publish_message("vumi", "test_ok_transport.outbound", rep) response = yield d self.assertEqual(response, 'OK')
class TestStreamingClient(VumiTestCase): @inlineCallbacks def setUp(self): self.mock_server = MockHttpServer(self.handle_request) self.add_cleanup(self.mock_server.stop) yield self.mock_server.start() self.url = self.mock_server.url self.client = StreamingClient() self.messages_received = DeferredQueue() self.errors_received = DeferredQueue() self.disconnects_received = DeferredQueue() def reason_trapper(reason): if reason.trap(ResponseDone): self.disconnects_received.put(reason.getErrorMessage()) self.receiver = self.client.stream( Message, self.messages_received.put, self.errors_received.put, self.url, on_disconnect=reason_trapper) def handle_request(self, request): self.mock_server.queue.put(request) return NOT_DONE_YET @inlineCallbacks def test_callback_on_disconnect(self): req = yield self.mock_server.queue.get() req.write( '%s\n' % (Message(foo='bar').to_json().encode('utf-8'),)) req.finish() message = yield self.messages_received.get() self.assertEqual(message['foo'], 'bar') reason = yield self.disconnects_received.get() # this is the error message we get when a ResponseDone is raised # which happens when the remote server closes the connection. self.assertEqual(reason, 'Response body fully received') @inlineCallbacks def test_invalid_json(self): req = yield self.mock_server.queue.get() req.write("Hello\n") req.finish() try: yield self.errors_received.get() except VumiBridgeInvalidJsonError, e: self.assertEqual(e.args, ("Hello",)) else:
class TestStreamingClient(VumiTestCase): @inlineCallbacks def setUp(self): self.mock_server = MockHttpServer(self.handle_request) self.add_cleanup(self.mock_server.stop) yield self.mock_server.start() self.url = self.mock_server.url self.client = StreamingClient() self.messages_received = DeferredQueue() self.errors_received = DeferredQueue() self.disconnects_received = DeferredQueue() def reason_trapper(reason): if reason.trap(ResponseDone): self.disconnects_received.put(reason.getErrorMessage()) self.receiver = self.client.stream(Message, self.messages_received.put, self.errors_received.put, self.url, on_disconnect=reason_trapper) def handle_request(self, request): self.mock_server.queue.put(request) return NOT_DONE_YET @inlineCallbacks def test_callback_on_disconnect(self): req = yield self.mock_server.queue.get() req.write('%s\n' % (Message(foo='bar').to_json().encode('utf-8'), )) req.finish() message = yield self.messages_received.get() self.assertEqual(message['foo'], 'bar') reason = yield self.disconnects_received.get() # this is the error message we get when a ResponseDone is raised # which happens when the remote server closes the connection. self.assertEqual(reason, 'Response body fully received') @inlineCallbacks def test_invalid_json(self): req = yield self.mock_server.queue.get() req.write("Hello\n") req.finish() try: yield self.errors_received.get() except VumiBridgeInvalidJsonError, e: self.assertEqual(e.args, ("Hello", )) else:
class TestGoConversationTransportBase(VumiTestCase): transport_class = None @inlineCallbacks def setUp(self): self.tx_helper = self.add_helper(TransportHelper(self.transport_class)) self.mock_server = MockHttpServer(self.handle_inbound_request) self.add_cleanup(self.mock_server.stop) self.clock = Clock() yield self.mock_server.start() self._pending_reqs = [] self.add_cleanup(self.finish_requests) @inlineCallbacks def get_transport(self, **config): defaults = { 'base_url': self.mock_server.url, 'account_key': 'account-key', 'conversation_key': 'conversation-key', 'access_token': 'access-token', } defaults.update(config) transport = yield self.tx_helper.get_transport(defaults) yield self.setup_transport(transport) returnValue(transport) def setup_transport(self, transport): pass @inlineCallbacks def finish_requests(self): for req in self._pending_reqs: if not req.finished: yield req.finish() def handle_inbound_request(self, request): self.mock_server.queue.put(request) return NOT_DONE_YET @inlineCallbacks def get_next_request(self): req = yield self.mock_server.queue.get() self._pending_reqs.append(req) returnValue(req)
class TestQuietGetPage(VumiTestCase): @inlineCallbacks def setUp(self): self.mock_http = MockHttpServer(self._handle_request) self.add_cleanup(self.mock_http.stop) yield self.mock_http.start() def _handle_request(self, request): request.setResponseCode(http.OK) request.do_not_log = True return "Hello" @inlineCallbacks def test_request(self): with LogCatcher() as lc: result = yield quiet_get_page(self.mock_http.url) self.assertEqual(lc.logs, []) self.assertEqual(result, "Hello")
class TestQuietGetPage(VumiTestCase): @inlineCallbacks def setUp(self): self.mock_http = MockHttpServer(self._handle_request) self.add_cleanup(self.mock_http.stop) yield self.mock_http.start() def _handle_request(self, request): request.setResponseCode(http.OK) request.do_not_log = True return "Hello" @inlineCallbacks def test_request(self): with LogCatcher() as lc: result = yield quiet_get_page(self.mock_http.url) self.assertEqual(lc.logs, []) self.assertEqual(result, "Hello")
class TestMessengerTransport(VumiTestCase): timeout = 1 @inlineCallbacks def setUp(self): self.clock = Clock() MessengerTransport.clock = self.clock self.remote_server = MockHttpServer(lambda _: 'OK') yield self.remote_server.start() self.addCleanup(self.remote_server.stop) self.tx_helper = self.add_helper( HttpRpcTransportHelper(PatchedMessengerTransport)) self.msg_helper = self.add_helper(MessageHelper()) connection_pool = HTTPConnectionPool(reactor, persistent=False) treq._utils.set_global_pool(connection_pool) @inlineCallbacks def mk_transport(self, **kw): config = { 'web_port': 0, 'web_path': '/api', 'publish_status': True, 'outbound_url': self.remote_server.url, 'username': '******', 'password': '******', } config.update(kw) transport = yield self.tx_helper.get_transport(config) transport.clock = self.clock returnValue(transport) @inlineCallbacks def test_hub_challenge(self): yield self.mk_transport() res = yield self.tx_helper.mk_request_raw( method='POST', params={ 'hub.challenge': 'foo', } ) self.assertEqual(res.code, http.OK) self.assertEqual(res.delivered_body, 'foo') @inlineCallbacks def test_setup_welcome_message(self): transport = yield self.mk_transport( access_token='access-token') d = transport.setup_welcome_message({ 'message': { 'text': 'This is the welcome message!' } }, 'app-id') (request_d, args, kwargs) = yield transport.request_queue.get() request_d.callback(DummyResponse(200, json.dumps({}))) method, url, data = args self.assertTrue('app-id' in url) self.assertTrue('?access_token=access-token' in url) self.assertEqual(json.loads(data)['call_to_actions'], { 'message': { 'text': 'This is the welcome message!' } }) yield d @inlineCallbacks def test_inbound(self): yield self.mk_transport() res = yield self.tx_helper.mk_request_raw( method='POST', data=json.dumps({ "object": "page", "entry": [{ "id": "PAGE_ID", "time": 1457764198246, "messaging": [{ "sender": { "id": "USER_ID" }, "recipient": { "id": "PAGE_ID" }, "timestamp": 1457764197627, "message": { "mid": "mid.1457764197618:41d102a3e1ae206a38", "seq": 73, "text": "hello, world!" } }] }] })) self.assertEqual(res.code, http.OK) [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg['from_addr'], 'USER_ID') self.assertEqual(msg['to_addr'], 'PAGE_ID') self.assertEqual(msg['from_addr_type'], 'facebook_messenger') self.assertEqual(msg['content'], 'hello, world!') self.assertEqual(msg['provider'], 'facebook') self.assertEqual(msg['transport_metadata'], { 'messenger': { 'mid': 'mid.1457764197618:41d102a3e1ae206a38' } }) statuses = self.tx_helper.get_dispatched_statuses() [response_status, inbound_status] = statuses self.assertEqual(response_status['status'], 'ok') self.assertEqual(response_status['component'], 'response') self.assertEqual(response_status['type'], 'response_sent') self.assertEqual(response_status['message'], 'Response sent') self.assertEqual(inbound_status['status'], 'ok') self.assertEqual(inbound_status['component'], 'inbound') self.assertEqual(inbound_status['type'], 'request_success') self.assertEqual(inbound_status['message'], 'Request successful') @inlineCallbacks def test_inbound_multiple(self): yield self.mk_transport() res = yield self.tx_helper.mk_request_raw( method='POST', data=json.dumps({ "object": "page", "entry": [{ "id": "PAGE_ID", "time": 1457764198246, "messaging": [{ "sender": { "id": "USER_ID1" }, "recipient": { "id": "PAGE_ID" }, "timestamp": 1457764197627, "message": { "mid": "mid.1457764197618:41d102a3e1ae206a38", "seq": 73, "text": "hello, world!" } }, { "sender": { "id": "USER_ID2" }, "recipient": { "id": "PAGE_ID" }, "timestamp": 1457764197627, "message": { "mid": "mid.1457764197618:41d102a3e1ae206a39", "seq": 74, "text": "hello, again!" } }] }] })) self.assertEqual(res.code, http.OK) [msg1, msg2] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg1['from_addr'], 'USER_ID1') self.assertEqual(msg1['to_addr'], 'PAGE_ID') self.assertEqual(msg1['from_addr_type'], 'facebook_messenger') self.assertEqual(msg1['content'], 'hello, world!') self.assertEqual(msg1['provider'], 'facebook') self.assertEqual(msg1['transport_metadata'], { 'messenger': { 'mid': 'mid.1457764197618:41d102a3e1ae206a38' } }) self.assertEqual(msg2['from_addr'], 'USER_ID2') self.assertEqual(msg2['to_addr'], 'PAGE_ID') self.assertEqual(msg2['from_addr_type'], 'facebook_messenger') self.assertEqual(msg2['content'], 'hello, again!') self.assertEqual(msg2['provider'], 'facebook') self.assertEqual(msg2['transport_metadata'], { 'messenger': { 'mid': 'mid.1457764197618:41d102a3e1ae206a39' } }) statuses = self.tx_helper.get_dispatched_statuses() [response_status, inbound_status] = statuses self.assertEqual(response_status['status'], 'ok') self.assertEqual(response_status['component'], 'response') self.assertEqual(response_status['type'], 'response_sent') self.assertEqual(response_status['message'], 'Response sent') self.assertEqual(inbound_status['status'], 'ok') self.assertEqual(inbound_status['component'], 'inbound') self.assertEqual(inbound_status['type'], 'request_success') self.assertEqual(inbound_status['message'], 'Request successful') @inlineCallbacks def test_inbound_attachments(self): yield self.mk_transport() res = yield self.tx_helper.mk_request_raw( method='POST', data=json.dumps({ "object": "page", "entry": [{ "id": "PAGE_ID", "time": 1457764198246, "messaging": [{ "sender": { "id": "USER_ID" }, "recipient": { "id": "PAGE_ID" }, "timestamp": 1457764197627, "message": { "mid": "mid.1457764197618:41d102a3e1ae206a37", "seq": 63, "attachments": [{ "type": "image", "payload": { "url": "IMAGE_URL" } }] } }] }] })) self.assertEqual(res.code, http.OK) [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg['from_addr'], 'USER_ID') self.assertEqual(msg['to_addr'], 'PAGE_ID') self.assertEqual(msg['from_addr_type'], 'facebook_messenger') self.assertEqual(msg['provider'], 'facebook') self.assertEqual(msg['content'], '') self.assertEqual(msg['transport_metadata'], { 'messenger': { 'mid': "mid.1457764197618:41d102a3e1ae206a37", "attachments": [{ "type": "image", "payload": { "url": "IMAGE_URL" } }] } }) @inlineCallbacks def test_inbound_optin(self): yield self.mk_transport() res = yield self.tx_helper.mk_request_raw( method='POST', data=json.dumps({ "object": "page", "entry": [{ "id": "PAGE_ID", "time": 1457764198246, "messaging": [{ "sender": { "id": "USER_ID" }, "recipient": { "id": "PAGE_ID" }, "timestamp": 1457764197627, "optin": { "ref": "PASS_THROUGH_PARAM" } }] }] })) self.assertEqual(res.code, http.OK) [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg['from_addr'], 'USER_ID') self.assertEqual(msg['to_addr'], 'PAGE_ID') self.assertEqual(msg['from_addr_type'], 'facebook_messenger') self.assertEqual(msg['provider'], 'facebook') self.assertEqual(msg['content'], '') self.assertEqual(msg['transport_metadata'], { 'messenger': { 'mid': None, "optin": { "ref": "PASS_THROUGH_PARAM" } } }) @inlineCallbacks def test_inbound_postback(self): yield self.mk_transport() res = yield self.tx_helper.mk_request_raw( method='POST', data=json.dumps({ "object": "page", "entry": [{ "id": "PAGE_ID", "time": 1457764198246, "messaging": [{ "sender": { "id": "USER_ID" }, "recipient": { "id": "PAGE_ID" }, "timestamp": 1457764197627, "postback": { "payload": json.dumps({ "content": "1", "in_reply_to": "12345", }) } }] }] })) self.assertEqual(res.code, http.OK) [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg['from_addr'], 'USER_ID') self.assertEqual(msg['to_addr'], 'PAGE_ID') self.assertEqual(msg['from_addr_type'], 'facebook_messenger') self.assertEqual(msg['provider'], 'facebook') self.assertEqual(msg['content'], '1') self.assertEqual(msg['in_reply_to'], '12345') self.assertEqual(msg['transport_metadata'], { 'messenger': { 'mid': None } }) @inlineCallbacks def test_inbound_postback_other(self): yield self.mk_transport() res = yield self.tx_helper.mk_request_raw( method='POST', data=json.dumps({ "object": "page", "entry": [{ "id": "PAGE_ID", "time": 1457764198246, "messaging": [{ "sender": { "id": "USER_ID" }, "recipient": { "id": "PAGE_ID" }, "timestamp": 1457764197627, "postback": { "payload": json.dumps({ "postback": "ocean" }) } }] }] })) self.assertEqual(res.code, http.OK) [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg['from_addr'], 'USER_ID') self.assertEqual(msg['to_addr'], 'PAGE_ID') self.assertEqual(msg['from_addr_type'], 'facebook_messenger') self.assertEqual(msg['provider'], 'facebook') self.assertEqual(msg['content'], '') self.assertEqual(msg['in_reply_to'], None) self.assertEqual(msg['transport_metadata'], { 'messenger': { 'mid': None, "postback": "ocean" } }) @inlineCallbacks def test_inbound_with_user_profile(self): transport = yield self.mk_transport( access_token='the-access-token', retrieve_profile=True) d = self.tx_helper.mk_request_raw( method='POST', data=json.dumps({ "object": "page", "entry": [{ "id": "PAGE_ID", "time": 1457764198246, "messaging": [{ "sender": { "id": "USER_ID" }, "recipient": { "id": "PAGE_ID" }, "timestamp": 1457764197627, "message": { "mid": "mid.1457764197618:41d102a3e1ae206a38", "seq": 73, "text": "hello, world!" } }] }] })) (request_d, args, kwargs) = yield transport.request_queue.get() method, url, data = args # NOTE: this is the URLencoding self.assertTrue('first_name%2Clast_name%2Cprofile_pic' in url) self.assertTrue('the-access-token' in url) request_d.callback(DummyResponse(200, json.dumps({ 'first_name': 'first-name', 'last_name': 'last-name', 'profile_pic': 'rather unpleasant', }))) res = yield d self.assertEqual(res.code, http.OK) [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg['helper_metadata'], { 'messenger': { 'mid': 'mid.1457764197618:41d102a3e1ae206a38', 'first_name': 'first-name', 'last_name': 'last-name', 'profile_pic': 'rather unpleasant' } }) @inlineCallbacks def test_outbound(self): transport = yield self.mk_transport(access_token='access_token') d = self.tx_helper.make_dispatch_outbound( from_addr='456', to_addr='+123', content='hi') (request_d, args, kwargs) = yield transport.request_queue.get() method, url, data = args self.assertEqual(json.loads(data), { 'message': { 'text': 'hi', }, 'recipient': { 'id': '+123', } }) request_d.callback(DummyResponse(200, json.dumps({ 'message_id': 'the-message-id' }))) msg = yield d [ack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assertEqual(ack['user_message_id'], msg['message_id']) self.assertEqual(ack['sent_message_id'], 'the-message-id') [status] = self.tx_helper.get_dispatched_statuses() self.assertEqual(status['status'], 'ok') self.assertEqual(status['component'], 'outbound') self.assertEqual(status['type'], 'request_success') self.assertEqual(status['message'], 'Request successful') @inlineCallbacks def test_construct_plain_reply(self): transport = yield self.mk_transport() msg = self.msg_helper.make_outbound('hello world', to_addr='123') self.assertEqual( transport.construct_reply(msg), { 'message': { 'text': 'hello world' }, 'recipient': { 'id': '123' } }) @inlineCallbacks def test_construct_button_reply(self): transport = yield self.mk_transport() msg = self.msg_helper.make_outbound( 'hello world', to_addr='123', helper_metadata={ 'messenger': { 'template_type': 'button', 'text': 'hello world', 'buttons': [{ 'title': 'Jupiter', 'payload': { 'content': '1', }, }, { 'type': 'web_url', 'title': 'Mars', 'url': 'http://test', }, { 'type': 'phone_number', 'title': 'Venus', 'payload': '+271234567', }] } }) self.assertEqual( transport.construct_reply(msg), { 'recipient': { 'id': '123', }, 'message': { 'attachment': { 'type': 'template', 'payload': { 'template_type': 'button', 'text': 'hello world', 'buttons': [ { 'type': 'postback', 'title': 'Jupiter', 'payload': '{"content":"1"}', }, { 'type': 'web_url', 'title': 'Mars', 'url': 'http://test', }, { 'type': 'phone_number', 'title': 'Venus', 'payload': '+271234567', } ] } } } }) @inlineCallbacks def test_construct_bad_button(self): transport = yield self.mk_transport() msg = self.msg_helper.make_outbound( 'hello world', to_addr='123', helper_metadata={ 'messenger': { 'template_type': 'button', 'text': 'hello world', 'buttons': [{ 'title': 'Jupiter', 'payload': { 'content': '1', }, }, { 'type': 'unknown', 'title': 'Mars', }] } }) with self.assertRaisesRegexp( UnsupportedMessage, 'Unknown button type "unknown"'): transport.construct_reply(msg) @inlineCallbacks def test_construct_quick_reply(self): transport = yield self.mk_transport() msg = self.msg_helper.make_outbound( 'hello world', to_addr='123', helper_metadata={ 'messenger': { 'template_type': 'quick', 'text': 'hello world', 'quick_replies': [{ 'title': 'Jupiter', 'payload': { 'content': '1', }, }, { 'type': 'text', 'title': 'Mars', 'payload': { 'content': '2', }, }, { 'type': 'text', 'title': 'Venus', 'image_url': 'http://image', 'payload': { 'content': '3', }, }, { 'type': 'location', }] } }) self.assertEqual( transport.construct_reply(msg), { 'recipient': { 'id': '123', }, 'message': { 'text': 'hello world', 'quick_replies': [ { 'content_type': 'text', 'title': 'Jupiter', 'payload': '{"content":"1"}', }, { 'content_type': 'text', 'title': 'Mars', 'payload': '{"content":"2"}', }, { 'content_type': 'text', 'title': 'Venus', 'payload': '{"content":"3"}', 'image_url': 'http://image', }, { 'content_type': 'location', }, ] } }) @inlineCallbacks def test_construct_bad_quick_reply(self): transport = yield self.mk_transport() msg = self.msg_helper.make_outbound( 'hello world', to_addr='123', helper_metadata={ 'messenger': { 'template_type': 'quick', 'text': 'hello world', 'quick_replies': [{ 'title': 'Jupiter', 'payload': { 'content': '1', }, }, { 'type': 'unknown', 'title': 'Mars', }] } }) with self.assertRaisesRegexp( UnsupportedMessage, 'Unknown quick reply type "unknown"'): transport.construct_reply(msg) @inlineCallbacks def test_construct_generic_reply(self): transport = yield self.mk_transport() msg = self.msg_helper.make_outbound( 'hello world', to_addr='123', helper_metadata={ 'messenger': { 'template_type': 'generic', 'elements': [{ 'title': 'hello world', 'subtitle': 'arf', 'item_url': 'http://test', 'buttons': [{ 'title': 'Jupiter', 'payload': { 'content': '1', }, }, { 'type': 'web_url', 'title': 'Mars', 'url': 'http://test', }, { 'type': 'element_share', }] }, { 'title': 'hello again', 'image_url': 'http://image', 'buttons': [{ 'title': 'Mercury', 'payload': { 'content': '1', }, }, { 'type': 'web_url', 'title': 'Venus', 'url': 'http://test', }] } ] } }) self.maxDiff = None self.assertEqual( transport.construct_reply(msg), { 'recipient': { 'id': '123', }, 'message': { 'attachment': { 'type': 'template', 'payload': { 'template_type': 'generic', 'elements': [{ 'title': 'hello world', 'subtitle': 'arf', 'item_url': 'http://test', 'buttons': [{ 'type': 'postback', 'title': 'Jupiter', 'payload': '{"content":"1"}', }, { 'type': 'web_url', 'title': 'Mars', 'url': 'http://test', }, { 'type': 'element_share', }] }, { 'title': 'hello again', 'image_url': 'http://image', 'buttons': [{ 'type': 'postback', 'title': 'Mercury', 'payload': '{"content":"1"}', }, { 'type': 'web_url', 'title': 'Venus', 'url': 'http://test', }] }] } } } }) @inlineCallbacks def test_construct_list_reply(self): transport = yield self.mk_transport() msg = self.msg_helper.make_outbound( 'hello world', to_addr='123', helper_metadata={ 'messenger': { 'template_type': 'list', # 'top_element_style': 'compact', 'elements': [{ 'title': 'hello world', 'subtitle': 'arf', 'default_action': { 'url': 'http://test', }, 'buttons': [{ 'title': 'Jupiter', 'payload': { 'content': '1', }, }, { 'type': 'web_url', 'title': 'Mars', 'url': 'http://test', }] }, { 'title': 'hello again', 'image_url': 'http://image', 'default_action': { 'url': 'http://test', 'webview_height_ratio': 'compact', 'messenger_extensions': False, 'fallback_url': 'http://moo' }, 'buttons': [{ 'title': 'Mercury', 'payload': { 'content': '2', }, }, { 'type': 'web_url', 'title': 'Venus', 'url': 'http://test', 'webview_height_ratio': 'tall', 'messenger_extensions': True, 'fallback_url': 'http://moo' }] } ], 'buttons': [{ 'title': 'Europa', 'payload': { 'content': '3', }, }] } }) self.maxDiff = None self.assertEqual( transport.construct_reply(msg), { 'recipient': { 'id': '123', }, 'message': { 'attachment': { 'type': 'template', 'payload': { 'template_type': 'list', 'top_element_style': 'compact', 'elements': [{ 'title': 'hello world', 'subtitle': 'arf', 'default_action': { 'type': 'web_url', 'url': 'http://test', }, 'buttons': [{ 'type': 'postback', 'title': 'Jupiter', 'payload': '{"content":"1"}', }, { 'type': 'web_url', 'title': 'Mars', 'url': 'http://test', }] }, { 'title': 'hello again', 'image_url': 'http://image', 'default_action': { 'type': 'web_url', 'url': 'http://test', 'webview_height_ratio': 'compact', 'messenger_extensions': False, 'fallback_url': 'http://moo' }, 'buttons': [{ 'type': 'postback', 'title': 'Mercury', 'payload': '{"content":"2"}', }, { 'type': 'web_url', 'title': 'Venus', 'url': 'http://test', 'webview_height_ratio': 'tall', 'messenger_extensions': True, 'fallback_url': 'http://moo' }] }], 'buttons': [{ 'type': 'postback', 'title': 'Europa', 'payload': '{"content":"3"}', }] } } } })
class TestNoStreamingHTTPWorkerBase(VumiTestCase): @inlineCallbacks def setUp(self): self.app_helper = self.add_helper( AppWorkerHelper(NoStreamingHTTPWorker)) self.config = { 'health_path': '/health/', 'web_path': '/foo', 'web_port': 0, 'metrics_prefix': 'metrics_prefix.', } self.app = yield self.app_helper.get_app_worker(self.config) self.addr = self.app.webserver.getHost() self.url = 'http://%s:%s%s' % ( self.addr.host, self.addr.port, self.config['web_path']) # Mock server to test HTTP posting of inbound messages & events self.mock_push_server = MockHttpServer(self.handle_request) yield self.mock_push_server.start() self.add_cleanup(self.mock_push_server.stop) self.push_calls = DeferredQueue() self.conversation = yield self.create_conversation( self.get_message_url(), self.get_event_url(), ['token-1', 'token-2', 'token-3']) self.auth_headers = { 'Authorization': ['Basic ' + base64.b64encode('%s:%s' % ( self.conversation.user_account.key, 'token-1'))], } self._setup_wait_for_request() self.add_cleanup(self._wait_for_requests) def get_message_url(self): return self.mock_push_server.url def get_event_url(self): return self.mock_push_server.url @inlineCallbacks def create_conversation(self, message_url, event_url, tokens): config = { 'http_api_nostream': { 'api_tokens': tokens, 'push_message_url': message_url, 'push_event_url': event_url, 'metric_store': 'metric_store', } } conv = yield self.app_helper.create_conversation(config=config) yield self.app_helper.start_conversation(conv) conversation = yield self.app_helper.get_conversation(conv.key) returnValue(conversation) def _setup_wait_for_request(self): # Hackery to wait for the request to finish self._req_state = { 'queue': DeferredQueue(), 'expected': 0, } orig_track = ConversationResource.track_request orig_release = ConversationResource.release_request def track_wrapper(*args, **kw): self._req_state['expected'] += 1 return orig_track(*args, **kw) def release_wrapper(*args, **kw): return orig_release(*args, **kw).addCallback( self._req_state['queue'].put) self.patch(ConversationResource, 'track_request', track_wrapper) self.patch(ConversationResource, 'release_request', release_wrapper) @inlineCallbacks def _wait_for_requests(self): while self._req_state['expected'] > 0: yield self._req_state['queue'].get() self._req_state['expected'] -= 1 def handle_request(self, request): self.push_calls.put(request) return NOT_DONE_YET def assert_bad_request(self, response, reason): self.assertEqual(response.code, http.BAD_REQUEST) data = json.loads(response.delivered_body) self.assertEqual(data, { "success": False, "reason": reason, })
class TestMediafoneTransport(VumiTestCase): @inlineCallbacks def setUp(self): self.mediafone_calls = DeferredQueue() self.mock_mediafone = MockHttpServer(self.handle_request) yield self.mock_mediafone.start() self.add_cleanup(self.mock_mediafone.stop) self.config = { 'web_path': "foo", 'web_port': 0, 'username': '******', 'password': '******', 'outbound_url': self.mock_mediafone.url, } self.tx_helper = self.add_helper(TransportHelper(MediafoneTransport)) self.transport = yield self.tx_helper.get_transport(self.config) self.transport_url = self.transport.get_transport_url() self.mediafonemc_response = '' self.mediafonemc_response_code = http.OK def handle_request(self, request): self.mediafone_calls.put(request) request.setResponseCode(self.mediafonemc_response_code) return self.mediafonemc_response def mkurl(self, content, from_addr="2371234567", **kw): params = { 'to': '12345', 'from': from_addr, 'sms': content, } 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='GET') [msg] = self.tx_helper.get_dispatched_inbound() self.assertEqual(msg['transport_name'], self.tx_helper.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_outbound(self): yield self.tx_helper.make_dispatch_outbound( "hello world", to_addr="2371234567") req = yield self.mediafone_calls.get() self.assertEqual(req.path, '/') self.assertEqual(req.method, 'GET') self.assertEqual({ 'username': ['user'], 'phone': ['2371234567'], 'password': ['pass'], 'msg': ['hello world'], }, req.args) @inlineCallbacks def test_nack(self): self.mediafonemc_response_code = http.NOT_FOUND self.mediafonemc_response = 'Not Found' msg = yield self.tx_helper.make_dispatch_outbound( "outbound", to_addr="2371234567") yield self.mediafone_calls.get() [nack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assertEqual(nack['user_message_id'], msg['message_id']) self.assertEqual(nack['sent_message_id'], msg['message_id']) self.assertEqual(nack['nack_reason'], 'Unexpected response code: 404') @inlineCallbacks def test_handle_non_ascii_input(self): url = self.mkurl(u"öæł".encode("utf-8")) response = yield http_request(url, '', method='GET') [msg] = self.tx_helper.get_dispatched_inbound() self.assertEqual(msg['transport_name'], self.tx_helper.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='GET') self.assertEqual(400, response.code) self.assertEqual(json.loads(response.delivered_body), {'unexpected_parameter': ['foo']}) @inlineCallbacks def test_missing_parameters(self): url = self.mkurl_raw(to='12345', sms='hello') response = yield http_request_full(url, '', method='GET') self.assertEqual(400, response.code) self.assertEqual(json.loads(response.delivered_body), {'missing_parameter': ['from']})
class TestVas2NetsTransport(VumiTestCase): transport_type = 'sms' @inlineCallbacks def setUp(self): self.config = { 'url': None, 'username': '******', 'password': '******', 'owner': 'owner', 'service': 'service', 'subservice': 'subservice', 'web_receive_path': '/receive', 'web_receipt_path': '/receipt', 'web_port': 0, } self.tx_helper = self.add_helper( TransportHelper(Vas2NetsTransport, transport_name='vas2nets')) self.transport = yield self.tx_helper.get_transport(self.config) self.transport_url = self.transport.get_transport_url() self.today = datetime.utcnow().date() def _make_handler(self, message_id, message, code, send_id): def handler(request): log.msg(request.content.read()) request.setResponseCode(code) required_fields = [ 'username', 'password', 'call-number', 'origin', 'text', 'messageid', 'provider', 'tariff', 'owner', 'service', 'subservice' ] log.msg('request.args', request.args) for key in required_fields: log.msg('checking for %s' % key) self.assertTrue(key in request.args) if send_id is not None: self.assertEqual(request.args['messageid'], [send_id]) if message_id: request.setHeader('X-Nth-Smsid', message_id) return message return handler @inlineCallbacks def start_mock_server(self, msg_id, msg, code=http.OK, send_id=None): self.mock_server = MockHttpServer( self._make_handler(msg_id, msg, code, send_id)) self.add_cleanup(self.mock_server.stop) yield self.mock_server.start() self.transport.config['url'] = self.mock_server.url def make_request(self, path, qparams): """ Builds a request URL with the appropriate params. """ args = { 'messageid': TransportMessage.generate_id(), 'time': self.today.strftime('%Y.%m.%d %H:%M:%S'), 'sender': '0041791234567', 'destination': '9292', 'provider': 'provider', 'keyword': '', 'header': '', 'text': '', 'keyword': '', } args.update(qparams) url = self.transport_url + path return http_request_full(url, urlencode(args), { 'Content-Type': ['application/x-www-form-urlencoded'], }) def make_delivery_report(self, status, tr_status, tr_message): transport_metadata = { 'delivery_message': tr_message, 'delivery_status': tr_status, 'network_id': 'provider', 'timestamp': self.today.strftime('%Y-%m-%dT%H:%M:%S'), } return self.tx_helper.make_delivery_report( self.tx_helper.make_outbound("foo", message_id="vas2nets.abc"), to_addr="+41791234567", delivery_status=status, transport_metadata=transport_metadata) def make_dispatch_outbound(self, content, **kw): transport_metadata = { 'original_message_id': 'vas2nets.def', 'keyword': '', 'network_id': 'provider', 'timestamp': self.today.strftime('%Y-%m-%dT%H:%M:%S'), } kw.setdefault('transport_metadata', transport_metadata) return self.tx_helper.make_dispatch_outbound(content, **kw) def assert_events_equal(self, expected, received): to_payload = lambda m: dict( (k, v) for k, v in m.payload.iteritems() if k not in ('event_id', 'timestamp', 'transport_type')) self.assertEqual(to_payload(expected), to_payload(received)) def assert_messages_equal(self, expected, received): to_payload = lambda m: dict( (k, v) for k, v in m.payload.iteritems() if k not in ('message_id', 'timestamp')) self.assertEqual(to_payload(expected), to_payload(received)) @inlineCallbacks def test_health_check(self): response = yield http_request_full(self.transport_url + "/health") self.assertEqual('OK', response.delivered_body) self.assertEqual(response.code, http.OK) @inlineCallbacks def test_receive_sms(self): response = yield self.make_request('/receive', { 'messageid': 'abc', 'text': 'hello world', }) self.assertEqual('', response.delivered_body) self.assertEqual(response.headers.getRawHeaders('content-type'), ['text/plain']) self.assertEqual(response.code, http.OK) [msg] = self.tx_helper.get_dispatched_inbound() expected_msg = self.tx_helper.make_inbound( "hello world", message_id='vas2nets.abc', transport_metadata={ 'original_message_id': 'vas2nets.abc', 'keyword': '', 'network_id': 'provider', 'timestamp': self.today.strftime('%Y-%m-%dT%H:%M:%S'), }) self.assert_messages_equal(expected_msg, msg) @inlineCallbacks def test_delivery_receipt_pending(self): response = yield self.make_request('/receipt', { 'smsid': '1', 'messageid': 'abc', 'sender': '+41791234567', 'status': '1', 'text': 'Message submitted to Provider for delivery.', }) self.assertEqual('', response.delivered_body) self.assertEqual(response.headers.getRawHeaders('content-type'), ['text/plain']) self.assertEqual(response.code, http.OK) msg = self.make_delivery_report( 'pending', '1', 'Message submitted to Provider for delivery.') [dr] = self.tx_helper.get_dispatched_events() self.assert_events_equal(msg, dr) @inlineCallbacks def test_delivery_receipt_failed(self): response = yield self.make_request('/receipt', { 'smsid': '1', 'messageid': 'abc', 'sender': '+41791234567', 'status': '-9', 'text': 'Message could not be delivered.', }) self.assertEqual('', response.delivered_body) self.assertEqual(response.headers.getRawHeaders('content-type'), ['text/plain']) self.assertEqual(response.code, http.OK) msg = self.make_delivery_report( 'failed', '-9', 'Message could not be delivered.') [dr] = self.tx_helper.get_dispatched_events() self.assert_events_equal(msg, dr) @inlineCallbacks def test_delivery_receipt_delivered(self): response = yield self.make_request('/receipt', { 'smsid': '1', 'messageid': 'abc', 'sender': '+41791234567', 'status': '2', 'text': 'Message delivered to MSISDN.', }) self.assertEqual('', response.delivered_body) self.assertEqual(response.headers.getRawHeaders('content-type'), ['text/plain']) self.assertEqual(response.code, http.OK) msg = self.make_delivery_report( 'delivered', '2', 'Message delivered to MSISDN.') [dr] = self.tx_helper.get_dispatched_events() self.assert_events_equal(msg, dr) def test_validate_characters(self): self.assertRaises(Vas2NetsEncodingError, validate_characters, u"ïøéå¬∆˚") self.assertTrue(validate_characters(string.ascii_lowercase)) self.assertTrue(validate_characters(string.ascii_uppercase)) self.assertTrue(validate_characters('0123456789')) self.assertTrue(validate_characters(u'äöü ÄÖÜ àùò ìèé §Ññ £$@')) self.assertTrue(validate_characters(u'/?!#%&()*+,-:;<=>.')) self.assertTrue(validate_characters(u'testing\ncarriage\rreturns')) self.assertTrue(validate_characters(u'testing "quotes"')) self.assertTrue(validate_characters(u"testing 'quotes'")) @inlineCallbacks def test_send_sms_success(self): mocked_message_id = TransportMessage.generate_id() mocked_message = "Result_code: 00, Message OK" # open an HTTP resource that mocks the Vas2Nets response for the # duration of this test yield self.start_mock_server(mocked_message_id, mocked_message) sent_msg = yield self.make_dispatch_outbound("hello") msg = self.tx_helper.make_ack( sent_msg, sent_message_id=mocked_message_id) [ack] = self.tx_helper.get_dispatched_events() self.assert_events_equal(msg, ack) @inlineCallbacks def test_send_sms_reply_success(self): mocked_message_id = TransportMessage.generate_id() reply_to_msgid = TransportMessage.generate_id() mocked_message = "Result_code: 00, Message OK" # open an HTTP resource that mocks the Vas2Nets response for the # duration of this test yield self.start_mock_server(mocked_message_id, mocked_message, send_id=reply_to_msgid) sent_msg = yield self.make_dispatch_outbound( "hello", in_reply_to=reply_to_msgid) msg = self.tx_helper.make_ack( sent_msg, sent_message_id=mocked_message_id) [ack] = self.tx_helper.get_dispatched_events() self.assert_events_equal(msg, ack) @inlineCallbacks def test_send_sms_fail(self): mocked_message_id = False mocked_message = ("Result_code: 04, Internal system error occurred " "while processing message") yield self.start_mock_server(mocked_message_id, mocked_message) msg = yield self.make_dispatch_outbound("hello") [twisted_failure] = self.flushLoggedErrors(Vas2NetsTransportError) failure = twisted_failure.value self.assertTrue("No SmsId Header" in str(failure)) [fmsg] = self.tx_helper.get_dispatched_failures() self.assertEqual(msg.payload, fmsg['message']) self.assertTrue( "Vas2NetsTransportError: No SmsId Header" in fmsg['reason']) [nack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assertEqual(nack['user_message_id'], msg['message_id']) self.assertEqual(nack['sent_message_id'], msg['message_id']) self.assertTrue("No SmsId Header" in nack['nack_reason']) @inlineCallbacks def test_send_sms_noconn(self): # TODO: Figure out a solution that doesn't require hoping that # nothing's listening on this port. self.transport.config['url'] = 'http://127.0.0.1:9999/' msg = yield self.make_dispatch_outbound("hello") [twisted_failure] = self.flushLoggedErrors(TemporaryFailure) failure = twisted_failure.value self.assertTrue("connection refused" in str(failure)) [fmsg] = self.tx_helper.get_dispatched_failures() self.assertEqual(msg.payload, fmsg['message']) self.assertEqual(fmsg['failure_code'], FailureMessage.FC_TEMPORARY) self.assertTrue(fmsg['reason'].strip().endswith("connection refused")) @inlineCallbacks def test_send_sms_not_OK(self): mocked_message = "Page not found." yield self.start_mock_server(None, mocked_message, http.NOT_FOUND) msg = yield self.make_dispatch_outbound("hello") [twisted_failure] = self.flushLoggedErrors(PermanentFailure) failure = twisted_failure.value self.assertTrue("server error: HTTP 404:" in str(failure)) [fmsg] = self.tx_helper.get_dispatched_failures() self.assertEqual(msg.payload, fmsg['message']) self.assertEqual(fmsg['failure_code'], FailureMessage.FC_PERMANENT) self.assertTrue(fmsg['reason'].strip() .endswith("server error: HTTP 404: Page not found.")) def test_normalize_outbound_msisdn(self): self.assertEqual(normalize_outbound_msisdn('+27761234567'), '0027761234567')
class TestStreamingHTTPWorker(VumiTestCase): @inlineCallbacks def setUp(self): self.app_helper = self.add_helper(AppWorkerHelper(StreamingHTTPWorker)) self.config = { 'health_path': '/health/', 'web_path': '/foo', 'web_port': 0, 'metrics_prefix': 'metrics_prefix.', } self.app = yield self.app_helper.get_app_worker(self.config) self.addr = self.app.webserver.getHost() self.url = 'http://%s:%s%s' % (self.addr.host, self.addr.port, self.config['web_path']) conv_config = { 'http_api': { 'api_tokens': [ 'token-1', 'token-2', 'token-3', ], 'metric_store': 'metric_store', } } conversation = yield self.app_helper.create_conversation( config=conv_config) yield self.app_helper.start_conversation(conversation) self.conversation = yield self.app_helper.get_conversation( conversation.key) self.auth_headers = { 'Authorization': [ 'Basic ' + base64.b64encode('%s:%s' % (conversation.user_account.key, 'token-1')) ], } self.client = StreamingClient() # Mock server to test HTTP posting of inbound messages & events self.mock_push_server = MockHttpServer(self.handle_request) yield self.mock_push_server.start() self.add_cleanup(self.mock_push_server.stop) self.push_calls = DeferredQueue() self._setup_wait_for_request() self.add_cleanup(self._wait_for_requests) def _setup_wait_for_request(self): # Hackery to wait for the request to finish self._req_state = { 'queue': DeferredQueue(), 'expected': 0, } orig_track = StreamingConversationResource.track_request orig_release = StreamingConversationResource.release_request def track_wrapper(*args, **kw): self._req_state['expected'] += 1 return orig_track(*args, **kw) def release_wrapper(*args, **kw): return orig_release(*args, **kw).addCallback(self._req_state['queue'].put) self.patch(StreamingConversationResource, 'track_request', track_wrapper) self.patch(StreamingConversationResource, 'release_request', release_wrapper) @inlineCallbacks def _wait_for_requests(self): while self._req_state['expected'] > 0: yield self._req_state['queue'].get() self._req_state['expected'] -= 1 def handle_request(self, request): self.push_calls.put(request) return NOT_DONE_YET @inlineCallbacks def pull_message(self, count=1): url = '%s/%s/messages.json' % (self.url, self.conversation.key) messages = DeferredQueue() errors = DeferredQueue() receiver = self.client.stream(TransportUserMessage, messages.put, errors.put, url, Headers(self.auth_headers)) received_messages = [] for msg_id in range(count): yield self.app_helper.make_dispatch_inbound('in %s' % (msg_id, ), message_id=str(msg_id), conv=self.conversation) recv_msg = yield messages.get() received_messages.append(recv_msg) receiver.disconnect() returnValue((receiver, received_messages)) def assert_bad_request(self, response, reason): self.assertEqual(response.code, http.BAD_REQUEST) data = json.loads(response.delivered_body) self.assertEqual(data, { "success": False, "reason": reason, }) @inlineCallbacks def test_proxy_buffering_headers_off(self): # This is the default, but we patch it anyway to make sure we're # testing the right thing should the default change. self.patch(StreamResourceMixin, 'proxy_buffering', False) receiver, received_messages = yield self.pull_message() headers = receiver._response.headers self.assertEqual(headers.getRawHeaders('x-accel-buffering'), ['no']) @inlineCallbacks def test_proxy_buffering_headers_on(self): self.patch(StreamResourceMixin, 'proxy_buffering', True) receiver, received_messages = yield self.pull_message() headers = receiver._response.headers self.assertEqual(headers.getRawHeaders('x-accel-buffering'), ['yes']) @inlineCallbacks def test_content_type(self): receiver, received_messages = yield self.pull_message() headers = receiver._response.headers self.assertEqual(headers.getRawHeaders('content-type'), ['application/json; charset=utf-8']) @inlineCallbacks def test_messages_stream(self): url = '%s/%s/messages.json' % (self.url, self.conversation.key) messages = DeferredQueue() errors = DeferredQueue() receiver = self.client.stream(TransportUserMessage, messages.put, errors.put, url, Headers(self.auth_headers)) msg1 = yield self.app_helper.make_dispatch_inbound( 'in 1', message_id='1', conv=self.conversation) msg2 = yield self.app_helper.make_dispatch_inbound( 'in 2', message_id='2', conv=self.conversation) rm1 = yield messages.get() rm2 = yield messages.get() receiver.disconnect() # Sometimes messages arrive out of order if we're hitting real redis. rm1, rm2 = sorted([rm1, rm2], key=lambda m: m['message_id']) self.assertEqual(msg1['message_id'], rm1['message_id']) self.assertEqual(msg2['message_id'], rm2['message_id']) self.assertEqual(errors.size, None) @inlineCallbacks def test_events_stream(self): url = '%s/%s/events.json' % (self.url, self.conversation.key) events = DeferredQueue() errors = DeferredQueue() receiver = yield self.client.stream(TransportEvent, events.put, events.put, url, Headers(self.auth_headers)) msg1 = yield self.app_helper.make_stored_outbound(self.conversation, 'out 1', message_id='1') ack1 = yield self.app_helper.make_dispatch_ack(msg1, conv=self.conversation) msg2 = yield self.app_helper.make_stored_outbound(self.conversation, 'out 2', message_id='2') ack2 = yield self.app_helper.make_dispatch_ack(msg2, conv=self.conversation) ra1 = yield events.get() ra2 = yield events.get() receiver.disconnect() self.assertEqual(ack1['event_id'], ra1['event_id']) self.assertEqual(ack2['event_id'], ra2['event_id']) self.assertEqual(errors.size, None) @inlineCallbacks def test_missing_auth(self): url = '%s/%s/messages.json' % (self.url, self.conversation.key) queue = DeferredQueue() receiver = self.client.stream(TransportUserMessage, queue.put, queue.put, url) response = yield receiver.get_response() self.assertEqual(response.code, http.UNAUTHORIZED) self.assertEqual(response.headers.getRawHeaders('www-authenticate'), ['basic realm="Conversation Realm"']) @inlineCallbacks def test_invalid_auth(self): url = '%s/%s/messages.json' % (self.url, self.conversation.key) queue = DeferredQueue() headers = Headers({ 'Authorization': ['Basic %s' % (base64.b64encode('foo:bar'), )], }) receiver = self.client.stream(TransportUserMessage, queue.put, queue.put, url, headers) response = yield receiver.get_response() self.assertEqual(response.code, http.UNAUTHORIZED) self.assertEqual(response.headers.getRawHeaders('www-authenticate'), ['basic realm="Conversation Realm"']) @inlineCallbacks def test_send_to(self): msg = { 'to_addr': '+2345', 'content': 'foo', 'message_id': 'evil_id', } # TaggingMiddleware.add_tag_to_msg(msg, self.tag) url = '%s/%s/messages.json' % (self.url, self.conversation.key) response = yield http_request_full(url, json.dumps(msg), self.auth_headers, method='PUT') self.assertEqual(response.code, http.OK) put_msg = json.loads(response.delivered_body) [sent_msg] = self.app_helper.get_dispatched_outbound() self.assertEqual(sent_msg['to_addr'], sent_msg['to_addr']) self.assertEqual( sent_msg['helper_metadata'], { 'go': { 'conversation_key': self.conversation.key, 'conversation_type': 'http_api', 'user_account': self.conversation.user_account.key, }, }) # We do not respect the message_id that's been given. self.assertNotEqual(sent_msg['message_id'], msg['message_id']) self.assertEqual(sent_msg['message_id'], put_msg['message_id']) self.assertEqual(sent_msg['to_addr'], msg['to_addr']) self.assertEqual(sent_msg['from_addr'], None) @inlineCallbacks def test_in_send_to_with_evil_content(self): msg = { 'content': 0xBAD, 'to_addr': '+1234', } url = '%s/%s/messages.json' % (self.url, self.conversation.key) response = yield http_request_full(url, json.dumps(msg), self.auth_headers, method='PUT') self.assert_bad_request( response, "Invalid or missing value for payload key 'content'") @inlineCallbacks def test_in_send_to_with_evil_to_addr(self): msg = { 'content': 'good', 'to_addr': 1234, } url = '%s/%s/messages.json' % (self.url, self.conversation.key) response = yield http_request_full(url, json.dumps(msg), self.auth_headers, method='PUT') self.assert_bad_request( response, "Invalid or missing value for payload key 'to_addr'") @inlineCallbacks def test_in_reply_to(self): inbound_msg = yield self.app_helper.make_stored_inbound( self.conversation, 'in 1', message_id='1') msg = { 'content': 'foo', 'in_reply_to': inbound_msg['message_id'], } url = '%s/%s/messages.json' % (self.url, self.conversation.key) response = yield http_request_full(url, json.dumps(msg), self.auth_headers, method='PUT') put_msg = json.loads(response.delivered_body) self.assertEqual(response.code, http.OK) [sent_msg] = self.app_helper.get_dispatched_outbound() self.assertEqual(sent_msg['to_addr'], put_msg['to_addr']) self.assertEqual( sent_msg['helper_metadata'], { 'go': { 'conversation_key': self.conversation.key, 'conversation_type': 'http_api', 'user_account': self.conversation.user_account.key, }, }) self.assertEqual(sent_msg['message_id'], put_msg['message_id']) self.assertEqual(sent_msg['session_event'], None) self.assertEqual(sent_msg['to_addr'], inbound_msg['from_addr']) self.assertEqual(sent_msg['from_addr'], '9292') @inlineCallbacks def test_in_reply_to_with_evil_content(self): inbound_msg = yield self.app_helper.make_stored_inbound( self.conversation, 'in 1', message_id='1') msg = { 'content': 0xBAD, 'in_reply_to': inbound_msg['message_id'], } url = '%s/%s/messages.json' % (self.url, self.conversation.key) response = yield http_request_full(url, json.dumps(msg), self.auth_headers, method='PUT') self.assert_bad_request( response, "Invalid or missing value for payload key 'content'") @inlineCallbacks def test_invalid_in_reply_to(self): msg = { 'content': 'foo', 'in_reply_to': '1', # this doesn't exist } url = '%s/%s/messages.json' % (self.url, self.conversation.key) response = yield http_request_full(url, json.dumps(msg), self.auth_headers, method='PUT') self.assert_bad_request(response, 'Invalid in_reply_to value') @inlineCallbacks def test_invalid_in_reply_to_with_missing_conversation_key(self): # create a message with no conversation inbound_msg = self.app_helper.make_inbound('in 1', message_id='msg-1') vumi_api = self.app_helper.vumi_helper.get_vumi_api() yield vumi_api.mdb.add_inbound_message(inbound_msg) msg = { 'content': 'foo', 'in_reply_to': inbound_msg['message_id'], } url = '%s/%s/messages.json' % (self.url, self.conversation.key) with LogCatcher(message='Invalid reply to message <Message .*>' ' which has no conversation key') as lc: response = yield http_request_full(url, json.dumps(msg), self.auth_headers, method='PUT') [error_log] = lc.messages() self.assert_bad_request(response, "Invalid in_reply_to value") self.assertTrue(inbound_msg['message_id'] in error_log) @inlineCallbacks def test_in_reply_to_with_evil_session_event(self): inbound_msg = yield self.app_helper.make_stored_inbound( self.conversation, 'in 1', message_id='1') msg = { 'content': 'foo', 'in_reply_to': inbound_msg['message_id'], 'session_event': 0xBAD5E55104, } url = '%s/%s/messages.json' % (self.url, self.conversation.key) response = yield http_request_full(url, json.dumps(msg), self.auth_headers, method='PUT') self.assert_bad_request( response, "Invalid or missing value for payload key 'session_event'") self.assertEqual(self.app_helper.get_dispatched_outbound(), []) @inlineCallbacks def test_in_reply_to_with_evil_message_id(self): inbound_msg = yield self.app_helper.make_stored_inbound( self.conversation, 'in 1', message_id='1') msg = { 'content': 'foo', 'in_reply_to': inbound_msg['message_id'], 'message_id': 'evil_id' } url = '%s/%s/messages.json' % (self.url, self.conversation.key) response = yield http_request_full(url, json.dumps(msg), self.auth_headers, method='PUT') self.assertEqual(response.code, http.OK) put_msg = json.loads(response.delivered_body) [sent_msg] = self.app_helper.get_dispatched_outbound() # We do not respect the message_id that's been given. self.assertNotEqual(sent_msg['message_id'], msg['message_id']) self.assertEqual(sent_msg['message_id'], put_msg['message_id']) self.assertEqual(sent_msg['to_addr'], inbound_msg['from_addr']) self.assertEqual(sent_msg['from_addr'], '9292') @inlineCallbacks def test_metric_publishing(self): metric_data = [ ("vumi.test.v1", 1234, 'SUM'), ("vumi.test.v2", 3456, 'AVG'), ] url = '%s/%s/metrics.json' % (self.url, self.conversation.key) response = yield http_request_full(url, json.dumps(metric_data), self.auth_headers, method='PUT') self.assertEqual(response.code, http.OK) prefix = "go.campaigns.test-0-user.stores.metric_store" self.assertEqual(self.app_helper.get_published_metrics(self.app), [("%s.vumi.test.v1" % prefix, 1234), ("%s.vumi.test.v2" % prefix, 3456)]) @inlineCallbacks def test_concurrency_limits(self): config = yield self.app.get_config(None) concurrency = config.concurrency_limit queue = DeferredQueue() url = '%s/%s/messages.json' % (self.url, self.conversation.key) max_receivers = [ self.client.stream(TransportUserMessage, queue.put, queue.put, url, Headers(self.auth_headers)) for _ in range(concurrency) ] for i in range(concurrency): msg = yield self.app_helper.make_dispatch_inbound( 'in %s' % (i, ), message_id=str(i), conv=self.conversation) received = yield queue.get() self.assertEqual(msg['message_id'], received['message_id']) maxed_out_resp = yield http_request_full(url, method='GET', headers=self.auth_headers) self.assertEqual(maxed_out_resp.code, 403) self.assertTrue( 'Too many concurrent connections' in maxed_out_resp.delivered_body) [r.disconnect() for r in max_receivers] @inlineCallbacks def test_disabling_concurrency_limit(self): conv_resource = StreamingConversationResource(self.app, self.conversation.key) # negative concurrency limit disables it ctxt = ConfigContext(user_account=self.conversation.user_account.key, concurrency_limit=-1) config = yield self.app.get_config(msg=None, ctxt=ctxt) self.assertTrue( (yield conv_resource.is_allowed(config, self.conversation.user_account.key))) @inlineCallbacks def test_backlog_on_connect(self): for i in range(10): yield self.app_helper.make_dispatch_inbound('in %s' % (i, ), message_id=str(i), conv=self.conversation) queue = DeferredQueue() url = '%s/%s/messages.json' % (self.url, self.conversation.key) receiver = self.client.stream(TransportUserMessage, queue.put, queue.put, url, Headers(self.auth_headers)) for i in range(10): received = yield queue.get() self.assertEqual(received['message_id'], str(i)) receiver.disconnect() @inlineCallbacks def test_health_response(self): health_url = 'http://%s:%s%s' % (self.addr.host, self.addr.port, self.config['health_path']) response = yield http_request_full(health_url, method='GET') self.assertEqual(response.delivered_body, '0') yield self.app_helper.make_dispatch_inbound('in 1', message_id='1', conv=self.conversation) queue = DeferredQueue() stream_url = '%s/%s/messages.json' % (self.url, self.conversation.key) stream_receiver = self.client.stream(TransportUserMessage, queue.put, queue.put, stream_url, Headers(self.auth_headers)) yield queue.get() response = yield http_request_full(health_url, method='GET') self.assertEqual(response.delivered_body, '1') stream_receiver.disconnect() response = yield http_request_full(health_url, method='GET') self.assertEqual(response.delivered_body, '0') self.assertEqual( self.app.client_manager.clients, {'sphex.stream.message.%s' % (self.conversation.key, ): []}) @inlineCallbacks def test_post_inbound_message(self): # Set the URL so stuff is HTTP Posted instead of streamed. self.conversation.config['http_api'].update({ 'push_message_url': self.mock_push_server.url, }) yield self.conversation.save() msg_d = self.app_helper.make_dispatch_inbound('in 1', message_id='1', conv=self.conversation) req = yield self.push_calls.get() posted_json_data = req.content.read() req.finish() msg = yield msg_d posted_msg = TransportUserMessage.from_json(posted_json_data) self.assertEqual(posted_msg['message_id'], msg['message_id']) @inlineCallbacks def test_post_inbound_message_201_response(self): # Set the URL so stuff is HTTP Posted instead of streamed. self.conversation.config['http_api'].update({ 'push_message_url': self.mock_push_server.url, }) yield self.conversation.save() with LogCatcher(message='Got unexpected response code') as lc: msg_d = self.app_helper.make_dispatch_inbound( 'in 1', message_id='1', conv=self.conversation) req = yield self.push_calls.get() req.setResponseCode(201) req.finish() yield msg_d self.assertEqual(lc.messages(), []) @inlineCallbacks def test_post_inbound_message_500_response(self): # Set the URL so stuff is HTTP Posted instead of streamed. self.conversation.config['http_api'].update({ 'push_message_url': self.mock_push_server.url, }) yield self.conversation.save() with LogCatcher(message='Got unexpected response code') as lc: msg_d = self.app_helper.make_dispatch_inbound( 'in 1', message_id='1', conv=self.conversation) req = yield self.push_calls.get() req.setResponseCode(500) req.finish() yield msg_d [warning_log] = lc.messages() self.assertTrue(self.mock_push_server.url in warning_log) self.assertTrue('500' in warning_log) @inlineCallbacks def test_post_inbound_event(self): # Set the URL so stuff is HTTP Posted instead of streamed. self.conversation.config['http_api'].update({ 'push_event_url': self.mock_push_server.url, }) yield self.conversation.save() msg = yield self.app_helper.make_stored_outbound(self.conversation, 'out 1', message_id='1') event_d = self.app_helper.make_dispatch_ack(msg, conv=self.conversation) req = yield self.push_calls.get() posted_json_data = req.content.read() req.finish() ack = yield event_d self.assertEqual(TransportEvent.from_json(posted_json_data), ack) @inlineCallbacks def test_bad_urls(self): def assert_not_found(url, headers={}): d = http_request_full(self.url, method='GET', headers=headers) d.addCallback(lambda r: self.assertEqual(r.code, http.NOT_FOUND)) return d yield assert_not_found(self.url) yield assert_not_found(self.url + '/') yield assert_not_found('%s/%s' % (self.url, self.conversation.key), headers=self.auth_headers) yield assert_not_found('%s/%s/' % (self.url, self.conversation.key), headers=self.auth_headers) yield assert_not_found('%s/%s/foo' % (self.url, self.conversation.key), headers=self.auth_headers) @inlineCallbacks def test_send_message_command(self): yield self.app_helper.dispatch_command( 'send_message', user_account_key=self.conversation.user_account.key, conversation_key=self.conversation.key, command_data={ u'batch_id': u'batch-id', u'content': u'foo', u'to_addr': u'to_addr', u'msg_options': { u'helper_metadata': { u'tag': { u'tag': [u'longcode', u'default10080'] } }, u'from_addr': u'default10080', } }) [msg] = self.app_helper.get_dispatched_outbound() self.assertEqual(msg.payload['to_addr'], "to_addr") self.assertEqual(msg.payload['from_addr'], "default10080") self.assertEqual(msg.payload['content'], "foo") self.assertEqual(msg.payload['message_type'], "user_message") self.assertEqual(msg.payload['helper_metadata']['go']['user_account'], self.conversation.user_account.key) self.assertEqual(msg.payload['helper_metadata']['tag']['tag'], ['longcode', 'default10080']) @inlineCallbacks def test_process_command_send_message_in_reply_to(self): msg = yield self.app_helper.make_stored_inbound( self.conversation, "foo") yield self.app_helper.dispatch_command( 'send_message', user_account_key=self.conversation.user_account.key, conversation_key=self.conversation.key, command_data={ u'batch_id': u'batch-id', u'content': u'foo', u'to_addr': u'to_addr', u'msg_options': { u'helper_metadata': { u'tag': { u'tag': [u'longcode', u'default10080'] } }, u'transport_name': u'smpp_transport', u'in_reply_to': msg['message_id'], u'transport_type': u'sms', u'from_addr': u'default10080', } }) [sent_msg] = self.app_helper.get_dispatched_outbound() self.assertEqual(sent_msg['to_addr'], msg['from_addr']) self.assertEqual(sent_msg['content'], 'foo') self.assertEqual(sent_msg['in_reply_to'], msg['message_id'])
class SimagriTransportTestCase(TransportTestCase): transport_name = 'simagri' transport_type = 'sms' transport_class = SimagriHttpTransport send_path = '/sendsms/index' send_port = 9999 @inlineCallbacks def setUp(self): yield super(SimagriTransportTestCase, self).setUp() self.simagri_sms_calls = DeferredQueue() self.mock_simagri_sms = MockHttpServer(self.handle_request) yield self.mock_simagri_sms.start() self.config = { 'transport_name': self.transport_name, 'receive_path': 'sendsms', 'receive_port': 9998, 'outbound_url': self.mock_simagri_sms.url } self.transport = yield self.get_transport(self.config) self.transport_url = self.transport.get_transport_url() @inlineCallbacks def tearDown(self): yield self.mock_simagri_sms.stop() yield super(SimagriTransportTestCase, self).tearDown() def handle_request(self, request): self.simagri_sms_calls.put(request) return '' @inlineCallbacks def test_sending_sms(self): yield self.dispatch(self.mkmsg_out(from_addr="+2261")) req = yield self.simagri_sms_calls.get() self.assertEqual(req.path, '/') self.assertEqual(req.method, 'GET') self.assertEqual({ 'from_addr': ['+2261'], 'message': ['hello world'], }, req.args) [smsg] = self.get_dispatched_events() self.assertEqual(self.mkmsg_ack(user_message_id='1', sent_message_id='1'), smsg) @inlineCallbacks def test_sending_sms_complex(self): yield self.dispatch(self.mkmsg_out( from_addr="+2261", content=u'setoffre #A#BELG=10/tete+300000/pu# envoy\xe9 depuis SIMAgriMobile')) req = yield self.simagri_sms_calls.get() self.assertEqual(req.path, '/') self.assertEqual(req.method, 'GET') self.assertEqual({ 'from_addr': ['+2261'], 'message': ['setoffre #A#BELG=10/tete+300000/pu# envoy\xc3\xa9 depuis SIMAgriMobile'], }, req.args) [smsg] = self.get_dispatched_events() self.assertEqual(self.mkmsg_ack(user_message_id='1', sent_message_id='1'), smsg) def mkurl_raw(self, **params): return '%s%s?%s' % ( self.transport_url, self.config['receive_path'], urlencode(params)) def mkurl(self, content, to_addr, from_addr, **kw): params = { 'message': content, 'to_addr': to_addr, 'from_addr': from_addr, } params.update(kw) return self.mkurl_raw(**params) @inlineCallbacks def test_receiving_sms(self): url = self.mkurl('Hello envoy\xc3\xa9', '+2261', '2323') response = yield http_request_full(url, method='GET') [smsg] = self.get_dispatched_messages() self.assertEqual(response.code, http.OK) self.assertEqual(u'Hello envoy\xe9', smsg['content']) self.assertEqual('+2261', smsg['to_addr']) self.assertEqual('2323', smsg['from_addr']) @inlineCallbacks def test_receiving_sms_accent(self): url = self.mkurl("Mr Zonga Salif veut vendre 11 t Maïs Blanc à 14909,09/t, Total HT 163999,99 contact:+22666486073 l'offre expire dans 11 jrs", '+2261', '2323') response = yield http_request_full(url, method='GET') [smsg] = self.get_dispatched_messages() self.assertEqual(response.code, http.OK) self.assertEqual(u"Mr Zonga Salif veut vendre 11 t Ma\xefs Blanc \xe0 14909,09/t, Total HT 163999,99 contact:+22666486073 l'offre expire dans 11 jrs", smsg['content']) self.assertEqual('+2261', smsg['to_addr']) self.assertEqual('2323', smsg['from_addr']) @inlineCallbacks def test_receiving_sms_fail(self): params = { 'message': 'Hello', 'to_addr': '+2261', } url = self.mkurl_raw(**params) response = yield http_request_full(url, method='GET') self.assertEqual(0, len(self.get_dispatched_messages())) self.assertEqual(response.code, http.INTERNAL_SERVER_ERROR)
class TestRapidSMSRelay(VumiTestCase): def setUp(self): self.app_helper = self.add_helper(ApplicationHelper(RapidSMSRelay)) @inlineCallbacks def setup_resource(self, callback=None, auth=None, config=None): if callback is None: callback = lambda r: self.fail("No RapidSMS requests expected.") self.mock_server = MockHttpServer(callback) self.add_cleanup(self.mock_server.stop) yield self.mock_server.start() url = '%s%s' % (self.mock_server.url, '/test/resource/path') self.app = yield self.setup_app(url, auth=auth, config=config) def setup_app(self, url, auth=None, config=None): vumi_username, vumi_password = auth if auth else (None, None) app_config = { 'rapidsms_url': url, 'web_path': '/send/', 'web_port': '0', 'rapidsms_username': '******', 'rapidsms_password': '******', 'vumi_username': vumi_username, 'vumi_password': vumi_password, 'allowed_endpoints': ['default', '10010', '10020'], } if config: app_config.update(config) return self.app_helper.get_application(app_config) def get_response_msgs(self, response): payloads = from_json(response.delivered_body) return [TransportUserMessage( _process_fields=False, **to_kwargs(payload)) for payload in payloads] @inlineCallbacks def test_rapidsms_relay_success(self): def cb(request): msg = TransportUserMessage.from_json(request.content.read()) self.assertEqual(msg['content'], 'hello world') self.assertEqual(msg['from_addr'], '+41791234567') return 'OK' yield self.setup_resource(cb) yield self.app_helper.make_dispatch_inbound("hello world") self.assertEqual([], self.app_helper.get_dispatched_outbound()) @inlineCallbacks def test_rapidsms_relay_unicode(self): def cb(request): msg = TransportUserMessage.from_json(request.content.read()) self.assertEqual(msg['content'], u'h\xc6llo') return 'OK' yield self.setup_resource(cb) yield self.app_helper.make_dispatch_inbound(u'h\xc6llo') self.assertEqual([], self.app_helper.get_dispatched_outbound()) @inlineCallbacks def test_rapidsms_relay_with_basic_auth(self): def cb(request): self.assertEqual(request.getUser(), 'username') self.assertEqual(request.getPassword(), 'password') msg = TransportUserMessage.from_json(request.content.read()) self.assertEqual(msg['message_id'], 'abc') self.assertEqual(msg['content'], 'hello world') self.assertEqual(msg['from_addr'], '+41791234567') return 'OK' yield self.setup_resource(cb) yield self.app_helper.make_dispatch_inbound( "hello world", message_id="abc") self.assertEqual([], self.app_helper.get_dispatched_outbound()) @inlineCallbacks def test_rapidsms_relay_with_bad_basic_auth(self): def cb(request): request.setResponseCode(http.UNAUTHORIZED) return 'Not Authorized' yield self.setup_resource(cb) yield self.app_helper.make_dispatch_inbound("hi") self.assertEqual([], self.app_helper.get_dispatched_outbound()) @inlineCallbacks def test_rapidsms_relay_logs_events(self): yield self.setup_resource() with LogCatcher() as lc: yield self.app_helper.make_dispatch_delivery_report( self.app_helper.make_outbound("foo", message_id="abc")) yield self.app_helper.make_dispatch_ack( self.app_helper.make_outbound("foo", message_id="1")) self.assertEqual(lc.messages(), [ "Delivery report received for message u'abc'," " status u'delivered'", "Acknowledgement received for message u'1'", ]) self.assertEqual([], self.app_helper.get_dispatched_outbound()) @inlineCallbacks def test_rapidsms_relay_with_unicode_rapidsms_http_method(self): def cb(request): msg = TransportUserMessage.from_json(request.content.read()) self.assertEqual(msg['content'], 'hello world') self.assertEqual(msg['from_addr'], '+41791234567') return 'OK' yield self.setup_resource(cb, config={"rapidsms_http_method": u"POST"}) yield self.app_helper.make_dispatch_inbound("hello world") self.assertEqual([], self.app_helper.get_dispatched_outbound()) def _call_relay(self, data, auth=None): data = json.dumps(data) host = self.app.web_resource.getHost() send_url = "http://127.0.0.1:%d/send" % (host.port,) headers = {} if auth is not None: headers['Authorization'] = basic_auth_string(*auth) return http_request_full(send_url, data, headers=headers) def _check_messages(self, response, expecteds): response_msgs = self.get_response_msgs(response) msgs = self.app_helper.get_dispatched_outbound() for rmsg, msg, expected in zip(response_msgs, msgs, expecteds): self.assertEqual(msg, rmsg) for k, v in expected.items(): self.assertEqual(msg[k], v) self.assertEqual(len(msgs), len(expecteds)) self.assertEqual(len(response_msgs), len(expecteds)) @inlineCallbacks def test_rapidsms_relay_outbound(self): yield self.setup_resource() response = yield self._call_relay({ 'to_addr': ['+123456'], 'content': 'foo', }) self.assertEqual(response.headers.getRawHeaders('content-type'), ['application/json; charset=utf-8']) self._check_messages(response, [ {'to_addr': '+123456', 'content': u'foo'}]) @inlineCallbacks def test_rapidsms_relay_outbound_unicode(self): yield self.setup_resource() response = yield self._call_relay({ 'to_addr': ['+123456'], 'content': u'f\xc6r', }) self._check_messages(response, [ {'to_addr': '+123456', 'content': u'f\xc6r'}]) @inlineCallbacks def test_rapidsms_relay_multiple_outbound(self): yield self.setup_resource() addresses = ['+123456', '+678901'] response = yield self._call_relay({ 'to_addr': addresses, 'content': 'foo', }) self._check_messages(response, [ {'to_addr': addr, 'content': u'foo'} for addr in addresses]) @inlineCallbacks def test_rapidsms_relay_reply(self): msg_id, to_addr = 'abc', '+1234' yield self.setup_resource(lambda r: 'OK') yield self.app_helper.make_dispatch_inbound( "foo", message_id=msg_id, from_addr=to_addr) response = yield self._call_relay({ 'to_addr': [to_addr], 'content': 'foo', 'in_reply_to': msg_id, }) self._check_messages(response, [ {'to_addr': to_addr, 'content': u'foo', 'in_reply_to': msg_id}]) @inlineCallbacks def test_rapidsms_relay_reply_unknown_msg(self): yield self.setup_resource() response = yield self._call_relay({ 'to_addr': ['+123456'], 'content': 'foo', 'in_reply_to': 'unknown_message_id', }) self.assertEqual(response.code, 400) self.assertEqual(response.delivered_body, "Original message u'unknown_message_id' not found.") [err] = self.flushLoggedErrors(BadRequestError) @inlineCallbacks def test_rapidsms_relay_outbound_authenticated(self): auth = ("username", "good-password") yield self.setup_resource(callback=None, auth=auth) response = yield self._call_relay({ 'to_addr': ['+123456'], 'content': u'f\xc6r', }, auth=auth) self._check_messages(response, [ {'to_addr': '+123456', 'content': u'f\xc6r'}]) @inlineCallbacks def test_rapidsms_relay_outbound_failed_authenticated(self): bad_auth = ("username", "bad-password") good_auth = ("username", "good-password") yield self.setup_resource(callback=None, auth=good_auth) response = yield self._call_relay({ 'to_addr': ['+123456'], 'content': u'f\xc6r', }, auth=bad_auth) self.assertEqual(response.code, 401) self.assertEqual(response.delivered_body, "Unauthorized") @inlineCallbacks def test_rapidsms_relay_outbound_on_specific_endpoint(self): yield self.setup_resource() response = yield self._call_relay({ 'to_addr': ['+123456'], 'content': u'foo', 'endpoint': '10010', }) self._check_messages(response, [ {'to_addr': '+123456', 'content': u'foo'}]) [msg] = self.app_helper.get_dispatched_outbound() self.assertEqual(msg['routing_metadata'], { 'endpoint_name': '10010', }) @inlineCallbacks def test_rapidsms_relay_outbound_on_default_endpoint(self): yield self.setup_resource() response = yield self._call_relay({ 'to_addr': ['+123456'], 'content': u'foo', }) self._check_messages(response, [ {'to_addr': '+123456', 'content': u'foo'}]) [msg] = self.app_helper.get_dispatched_outbound() self.assertEqual(msg['routing_metadata'], { 'endpoint_name': 'default', }) @inlineCallbacks def test_rapidsms_relay_outbound_on_invalid_endpoint(self): yield self.setup_resource() response = yield self._call_relay({ 'to_addr': ['+123456'], 'content': u'foo', 'endpoint': u'bar', }) self.assertEqual([], self.app_helper.get_dispatched_outbound()) self.assertEqual(response.code, 400) self.assertEqual(response.delivered_body, "Endpoint u'bar' not defined in list of allowed" " endpoints ['default', '10010', '10020']") [err] = self.flushLoggedErrors(BadRequestError) @inlineCallbacks def test_rapidsms_relay_outbound_on_invalid_to_addr(self): yield self.setup_resource() response = yield self._call_relay({ 'to_addr': '+123456', 'content': u'foo', 'endpoint': u'bar', }) self.assertEqual([], self.app_helper.get_dispatched_outbound()) self.assertEqual(response.code, 400) self.assertEqual(response.delivered_body, "Supplied `to_addr` (u'+123456') was not a list.") [err] = self.flushLoggedErrors(BadRequestError)
class AirtelBfTransportTestCase(TransportTestCase): transport_name = 'airtel' transport_type = 'sms' transport_class = AirtelBfHttpTransport send_path = '/sendsms/index' send_port = 9999 @inlineCallbacks def setUp(self): yield super(AirtelBfTransportTestCase, self).setUp() self.airtel_sms_calls = DeferredQueue() self.mock_airtel_sms = MockHttpServer(self.handle_request) yield self.mock_airtel_sms.start() self.config = { 'transport_name': self.transport_name, 'receive_path': 'sendsms', 'receive_port': 9998, 'outbound_url': self.mock_airtel_sms.url, 'default_shortcode': '3411', 'login': '******', 'password': '******' } self.transport = yield self.get_transport(self.config) self.transport_url = self.transport.get_transport_url() @inlineCallbacks def tearDown(self): yield self.mock_airtel_sms.stop() yield super(AirtelBfTransportTestCase, self).tearDown() def handle_request(self, request): self.airtel_sms_calls.put(request) return '' @inlineCallbacks def test_sending_sms(self): yield self.dispatch(self.mkmsg_out()) req = yield self.airtel_sms_calls.get() self.assertEqual(req.path, '/') self.assertEqual(req.method, 'GET') self.assertEqual({ 'DA': ['9292'], 'SOA': ['+41791234567'], 'content': ['hello world'], 'u': ['texttochange'], 'p': ['password'] }, req.args) [smsg] = self.get_dispatched_events() self.assertEqual( self.mkmsg_ack(user_message_id='1', sent_message_id='1'), smsg) @inlineCallbacks def test_sending_sms_complex(self): yield self.dispatch(self.mkmsg_out( from_addr="+2261", content=u'setoffre #A#BELG=10/tete+300000/pu# envoy\xe9 depuis SIMAgriMobile')) req = yield self.airtel_sms_calls.get() self.assertEqual(req.path, '/') self.assertEqual(req.method, 'GET') self.assertEqual( 'setoffre #A#BELG=10/tete+300000/pu# envoyé depuis SIMAgriMobile', req.args['content'][0]) [smsg] = self.get_dispatched_events() self.assertEqual( self.mkmsg_ack(user_message_id='1', sent_message_id='1'), smsg) def mkurl_raw(self, **params): return '%s%s?%s' % ( self.transport_url, self.config['receive_path'], urlencode(params)) def mkurl(self, content, from_addr, **kw): params = { 'message': content, 'msisdn': from_addr} params.update(kw) return self.mkurl_raw(**params) @inlineCallbacks def test_receiving_sms(self): url = self.mkurl('Hello envoy\xc3\xa9', '+2261') response = yield http_request_full(url, method='GET') [smsg] = self.get_dispatched_messages() self.assertEqual(response.code, http.OK) self.assertEqual(u'Hello envoy\xe9', smsg['content']) self.assertEqual('3411', smsg['to_addr']) self.assertEqual('+2261', smsg['from_addr']) @inlineCallbacks def test_receiving_sms_accent(self): url = self.mkurl('Hello envoyé', '+2261') response = yield http_request_full(url, method='GET') [smsg] = self.get_dispatched_messages() self.assertEqual(response.code, http.OK) self.assertEqual(u'Hello envoy\xe9', smsg['content']) self.assertEqual('3411', smsg['to_addr']) self.assertEqual('+2261', smsg['from_addr']) @inlineCallbacks def test_receiving_sms_fail(self): params = { 'message': 'Hello', 'to_addr': '+2261', } url = self.mkurl_raw(**params) response = yield http_request_full(url, method='GET') self.assertEqual(0, len(self.get_dispatched_messages())) self.assertEqual(response.code, http.INTERNAL_SERVER_ERROR)
class TestVas2NetsSmsTransport(VumiTestCase): @inlineCallbacks def setUp(self): self.clock = Clock() self.patch(Vas2NetsSmsTransport, 'get_clock', lambda _: self.clock) self.remote_request_handler = lambda _: 'OK.1234' self.remote_server = MockHttpServer(self.remote_handle_request) yield self.remote_server.start() self.addCleanup(self.remote_server.stop) self.tx_helper = self.add_helper( HttpRpcTransportHelper(Vas2NetsSmsTransport)) connection_pool = HTTPConnectionPool(reactor, persistent=False) treq._utils.set_global_pool(connection_pool) @inlineCallbacks def mk_transport(self, **kw): config = { 'web_port': 0, 'web_path': '/api/v1/vas2nets/sms/', 'publish_status': True, 'outbound_url': urljoin(self.remote_server.url, 'nonreply'), 'username': '******', 'password': '******', } config.update(kw) transport = yield self.tx_helper.get_transport(config) self.patch(transport, 'get_clock', lambda _: self.clock) returnValue(transport) @inlineCallbacks def patch_reactor_call_later(self): yield self.wait_for_test_setup() self.patch(reactor, 'callLater', self.clock.callLater) def wait_for_test_setup(self): """ Wait for test setup to complete. Twisted's twisted.trial._asynctest runner calls `reactor.callLater` to set the test timeout *after* running the start of the test. We thus need to wait for this to happen *before* we patch `reactor.callLater`. """ d = Deferred() reactor.callLater(0, d.callback, None) return d def capture_remote_requests(self, response='OK.1234'): def handler(req): reqs.append(req) return response reqs = [] self.remote_request_handler = handler return reqs def remote_handle_request(self, req): return self.remote_request_handler(req) def get_host(self, transport): addr = transport.web_resource.getHost() return '%s:%s' % (addr.host, addr.port) def assert_contains_items(self, obj, items): for name, value in items.iteritems(): self.assertEqual(obj[name], value) def assert_uri(self, actual_uri, path, params): actual_path, actual_params = actual_uri.split('?') self.assertEqual(actual_path, path) self.assertEqual( sorted(actual_params.split('&')), sorted(urlencode(params).split('&'))) def assert_request_params(self, transport, req, params): self.assert_contains_items(req, { 'method': 'GET', 'path': transport.config['web_path'], 'content': '', 'headers': { 'Connection': ['close'], 'Host': [self.get_host(transport)] } }) self.assert_uri(req['uri'], transport.config['web_path'], params) @inlineCallbacks def test_inbound(self): yield self.mk_transport() res = yield self.tx_helper.mk_request( sender='+123', receiver='456', msgdata='hi', operator='MTN', recvtime='2012-02-27 19-50-07', msgid='789') self.assertEqual(res.code, http.OK) [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assert_contains_items(msg, { 'from_addr': '+123', 'from_addr_type': 'msisdn', 'to_addr': '456', 'content': 'hi', 'provider': 'MTN', 'transport_metadata': { 'vas2nets_sms': {'msgid': '789'} } }) [status] = self.tx_helper.get_dispatched_statuses() self.assert_contains_items(status, { 'status': 'ok', 'component': 'inbound', 'type': 'request_success', 'message': 'Request successful', }) @inlineCallbacks def test_inbound_decode_error(self): transport = yield self.mk_transport() with LogCatcher() as lc: res = yield self.tx_helper.mk_request( sender='+123', receiver='456', msgdata=u'ポケモン'.encode('utf-16'), operator='MTN', recvtime='2012-02-27 19-50-07', msgid='789') [error] = lc.errors[0]['message'] self.assertTrue("Bad request encoding" in error) req = json.loads(res.delivered_body)['invalid_request'] self.assert_request_params(transport, req, { 'sender': '+123', 'receiver': '456', 'msgdata': u'ポケモン'.encode('utf-16'), 'operator': 'MTN', 'recvtime': '2012-02-27 19-50-07', 'msgid': '789' }) [status] = self.tx_helper.get_dispatched_statuses() self.assert_contains_items(status, { 'status': 'down', 'component': 'inbound', 'type': 'request_decode_error', 'message': 'Bad request encoding', }) self.assert_request_params(transport, status['details']['request'], { 'sender': '+123', 'receiver': '456', 'msgdata': u'ポケモン'.encode('utf-16'), 'operator': 'MTN', 'recvtime': '2012-02-27 19-50-07', 'msgid': '789' }) @inlineCallbacks def test_inbound_bad_params(self): transport = yield self.mk_transport() with LogCatcher() as lc: res = yield self.tx_helper.mk_request( sender='+123', foo='456', operator='MTN', recvtime='2012-02-27 19-50-07', msgid='789') [error] = lc.errors[0]['message'] self.assertTrue("Bad request fields for inbound message" in error) self.assertTrue("foo" in error) self.assertTrue("msgdata" in error) self.assertTrue("receiver" in error) body = json.loads(res.delivered_body) self.assertEqual( body['unexpected_parameter'], ['foo']) self.assertEqual( sorted(body['missing_parameter']), ['msgdata', 'receiver']) [status] = self.tx_helper.get_dispatched_statuses() self.assert_contains_items(status, { 'status': 'down', 'component': 'inbound', 'type': 'request_bad_fields', 'message': 'Bad request fields', }) self.assert_request_params(transport, status['details']['request'], { 'sender': '+123', 'foo': '456', 'operator': 'MTN', 'recvtime': '2012-02-27 19-50-07', 'msgid': '789' }) self.assertEqual( status['details']['errors']['unexpected_parameter'], ['foo']) self.assertEqual( sorted(status['details']['errors']['missing_parameter']), ['msgdata', 'receiver']) @inlineCallbacks def test_outbound_non_reply(self): yield self.mk_transport( outbound_url=urljoin(self.remote_server.url, 'nonreply'), reply_outbound_url=urljoin(self.remote_server.url, 'reply')) reqs = self.capture_remote_requests() msg = yield self.tx_helper.make_dispatch_outbound( from_addr='456', to_addr='+123', content='hi') [req] = reqs self.assertTrue(req.uri.startswith('/nonreply')) self.assertEqual(req.method, 'GET') self.assertEqual(req.args, { 'username': ['root'], 'message': ['hi'], 'password': ['t00r'], 'sender': ['456'], 'receiver': ['+123'], 'message_type': ['1'], }) [ack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assert_contains_items(ack, { 'user_message_id': msg['message_id'], 'sent_message_id': msg['message_id'], }) [status] = self.tx_helper.get_dispatched_statuses() self.assert_contains_items(status, { 'status': 'ok', 'component': 'outbound', 'type': 'request_success', 'message': 'Request successful', }) @inlineCallbacks def test_outbound_reply(self): yield self.mk_transport( outbound_url=urljoin(self.remote_server.url, 'nonreply'), reply_outbound_url=urljoin(self.remote_server.url, 'reply')) reqs = self.capture_remote_requests() yield self.tx_helper.mk_request( sender='+123', receiver='456', msgdata='hi', operator='MTN', recvtime='2012-02-27 19-50-07', msgid='789') [in_msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) msg = in_msg.reply('hi back') self.tx_helper.clear_dispatched_statuses() yield self.tx_helper.dispatch_outbound(msg) [req] = reqs self.assertTrue(req.uri.startswith('/reply')) self.assertEqual(req.method, 'GET') self.assertEqual(req.args, { 'username': ['root'], 'message': ['hi back'], 'password': ['t00r'], 'sender': ['456'], 'receiver': ['+123'], 'message_id': ['789'], 'message_type': ['1'], }) [ack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assert_contains_items(ack, { 'user_message_id': msg['message_id'], 'sent_message_id': msg['message_id'], }) [status] = self.tx_helper.get_dispatched_statuses() self.assert_contains_items(status, { 'status': 'ok', 'component': 'outbound', 'type': 'request_success', 'message': 'Request successful', }) @inlineCallbacks def test_outbound_reply_nourl(self): yield self.mk_transport( outbound_url=urljoin(self.remote_server.url, 'nonreply'), reply_outbound_url=None) reqs = self.capture_remote_requests() yield self.tx_helper.mk_request( sender='+123', receiver='456', msgdata='hi', operator='MTN', recvtime='2012-02-27 19-50-07', msgid='789') [in_msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) msg = in_msg.reply('hi back') self.tx_helper.clear_dispatched_statuses() yield self.tx_helper.dispatch_outbound(msg) [req] = reqs self.assertTrue(req.uri.startswith('/nonreply')) self.assertEqual(req.method, 'GET') self.assertEqual(req.args, { 'username': ['root'], 'message': ['hi back'], 'password': ['t00r'], 'sender': ['456'], 'receiver': ['+123'], 'message_type': ['1'], }) [ack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assert_contains_items(ack, { 'user_message_id': msg['message_id'], 'sent_message_id': msg['message_id'], }) [status] = self.tx_helper.get_dispatched_statuses() self.assert_contains_items(status, { 'status': 'ok', 'component': 'outbound', 'type': 'request_success', 'message': 'Request successful', }) @inlineCallbacks def test_outbound_known_error(self): def handler(req): req.setResponseCode(200) [error] = req.args['message'] return error transport = yield self.mk_transport() self.remote_request_handler = handler nacks = {} statuses = {} errors = { 'ERR-11': 'ERR-11 Missing username', 'ERR-12': 'ERR-12 Missing password', 'ERR-13': 'ERR-13 Missing destination', 'ERR-14': 'ERR-14 Missing sender id', 'ERR-15': 'ERR-15 Missing message', 'ERR-21': 'ERR-21 Ender id too long', 'ERR-33': 'ERR-33 Invalid login', 'ERR-41': 'ERR-41 Insufficient credit', 'ERR-70': 'ERR-70 Invalid destination number', 'ERR-51': 'ERR-51 Invalid message id', 'ERR-52': 'ERR-52 System error', } for code, message in errors.items(): msg = yield self.tx_helper.make_dispatch_outbound( from_addr='456', to_addr='+123', content=message) [nack] = yield self.tx_helper.wait_for_dispatched_events(1) [status] = self.tx_helper.get_dispatched_statuses() self.tx_helper.clear_dispatched_events() self.tx_helper.clear_dispatched_statuses() nacks[code] = nack statuses[code] = status self.assert_contains_items(nack, { 'event_type': 'nack', 'user_message_id': msg['message_id'], 'sent_message_id': msg['message_id'], }) self.assert_contains_items(status, { 'status': 'down', 'component': 'outbound', }) nack_reasons = map_get(nacks, 'nack_reason') self.assertEqual(nack_reasons, { 'ERR-11': 'Missing username', 'ERR-12': 'Missing password', 'ERR-13': 'Missing destination', 'ERR-14': 'Missing sender id', 'ERR-15': 'Missing message', 'ERR-21': 'Ender id too long', 'ERR-33': 'Invalid login', 'ERR-41': 'Insufficient credit', 'ERR-70': 'Invalid destination number', 'ERR-51': 'Invalid message id', 'ERR-52': 'System error', }) self.assertEqual(nack_reasons, map_get(statuses, 'message')) self.assertEqual(map_get(statuses, 'type'), transport.SEND_FAIL_TYPES) @inlineCallbacks def test_outbound_unknown_error(self): def handler(req): req.setResponseCode(200) return 'ERR-99 Basao' yield self.mk_transport() self.remote_request_handler = handler msg = yield self.tx_helper.make_dispatch_outbound( from_addr='456', to_addr='+123', content='hi') [nack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assert_contains_items(nack, { 'event_type': 'nack', 'user_message_id': msg['message_id'], 'sent_message_id': msg['message_id'], 'nack_reason': 'Basao', }) [status] = self.tx_helper.get_dispatched_statuses() self.assert_contains_items(status, { 'status': 'down', 'component': 'outbound', 'type': 'request_fail_unknown', 'message': 'Basao', }) @inlineCallbacks def test_outbound_error_status_code(self): def handler(req): req.setResponseCode(502) return 'Bad Gateway' yield self.mk_transport() self.remote_request_handler = handler msg = yield self.tx_helper.make_dispatch_outbound( from_addr='456', to_addr='+123', content='hi') [nack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assert_contains_items(nack, { 'event_type': 'nack', 'user_message_id': msg['message_id'], 'sent_message_id': msg['message_id'], 'nack_reason': 'Bad Gateway', }) [status] = self.tx_helper.get_dispatched_statuses() self.assert_contains_items(status, { 'status': 'down', 'component': 'outbound', 'type': 'request_fail_unknown', 'message': 'Bad Gateway', }) @inlineCallbacks def test_outbound_missing_fields(self): yield self.mk_transport() msg = yield self.tx_helper.make_dispatch_outbound( from_addr='456', to_addr='+123', content=None) [nack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assert_contains_items(nack, { 'event_type': 'nack', 'user_message_id': msg['message_id'], 'sent_message_id': msg['message_id'], 'nack_reason': 'Missing fields: content', }) @inlineCallbacks def test_outbound_timeout(self): self.remote_request_handler = lambda _: NOT_DONE_YET yield self.mk_transport(outbound_request_timeout=3) msg = self.tx_helper.make_outbound( from_addr='456', to_addr='+123', content='hi') yield self.patch_reactor_call_later() d = self.tx_helper.dispatch_outbound(msg) self.clock.advance(0) # trigger initial request self.clock.advance(2) # wait 2 seconds of timeout self.assertEqual(self.tx_helper.get_dispatched_statuses(), []) self.clock.advance(1) # wait last second of timeout yield d [nack] = yield self.tx_helper.get_dispatched_events() self.assert_contains_items(nack, { 'event_type': 'nack', 'user_message_id': msg['message_id'], 'sent_message_id': msg['message_id'], 'nack_reason': 'Request timeout', }) [status] = self.tx_helper.get_dispatched_statuses() self.assert_contains_items(status, { 'status': 'down', 'component': 'outbound', 'type': 'request_timeout', 'message': 'Request timeout', })
class TestIntegratTransport(TestCase): timeout = 5 @inlineCallbacks def setUp(self): self.integrat_calls = DeferredQueue() self.mock_integrat = MockHttpServer(self.handle_request) yield self.mock_integrat.start() config = { 'transport_name': 'testgrat', 'web_path': "foo", 'web_port': "0", 'url': self.mock_integrat.url, 'username': '******', 'password': '******', } self.worker = get_stubbed_worker(IntegratTransport, config) self.broker = self.worker._amqp_client.broker yield self.worker.startWorker() addr = self.worker.web_resource.getHost() self.worker_url = "http://%s:%s/" % (addr.host, addr.port) @inlineCallbacks def tearDown(self): yield self.worker.stopWorker() yield self.mock_integrat.stop() def handle_request(self, request): self.integrat_calls.put(request) return '' @inlineCallbacks def test_health(self): result = yield http_request(self.worker_url + "health", "", method='GET') self.assertEqual(result, "OK") @inlineCallbacks def test_outbound(self): msg = TransportUserMessage(to_addr="12345", from_addr="56789", transport_name="testgrat", transport_type="ussd", transport_metadata={ 'session_id': "sess123", }, ) self.broker.publish_message("vumi", "testgrat.outbound", msg) req = yield self.integrat_calls.get() self.assertEqual(req.path, '/') self.assertEqual(req.method, 'POST') self.assertEqual(req.getHeader('content-type'), 'text/xml; charset=utf-8') self.assertEqual(req.content.getvalue(), '<Message><Version Version="1.0" />' '<Request Flags="0" SessionID="sess123"' ' Type="USSReply">' '<UserID Orientation="TR">testuser</UserID>' '<Password>testpass</Password>' '<USSText Type="TEXT" />' '</Request></Message>') @inlineCallbacks def test_inbound(self): xml = XML_TEMPLATE % { 'ussd_type': 'Request', 'sid': 'sess1234', 'network_sid': "netsid12345", 'msisdn': '27345', 'connstr': '*120*99#', 'text': 'foobar', } yield http_request(self.worker_url + "foo", xml, method='GET') msg, = yield self.broker.wait_messages("vumi", "testgrat.inbound", 1) payload = msg.payload self.assertEqual(payload['transport_name'], "testgrat") self.assertEqual(payload['transport_type'], "ussd") self.assertEqual(payload['transport_metadata'], {"session_id": "sess1234"}) self.assertEqual(payload['session_event'], TransportUserMessage.SESSION_RESUME) self.assertEqual(payload['from_addr'], '27345') self.assertEqual(payload['to_addr'], '*120*99#') self.assertEqual(payload['content'], 'foobar') @inlineCallbacks def test_inbound_non_ascii(self): xml = (XML_TEMPLATE % { 'ussd_type': 'Request', 'sid': 'sess1234', 'network_sid': "netsid12345", 'msisdn': '27345', 'connstr': '*120*99#', 'text': u'öæł', }).encode("utf-8") yield http_request(self.worker_url + "foo", xml, method='GET') msg, = yield self.broker.wait_messages("vumi", "testgrat.inbound", 1) payload = msg.payload self.assertEqual(payload['content'], u'öæł')
class TestAppositTransport(VumiTestCase): @inlineCallbacks def setUp(self): self.mock_server = MockHttpServer(self.handle_inbound_request) self.outbound_requests = DeferredQueue() self.mock_server_response = '' self.mock_server_response_code = http.OK yield self.mock_server.start() self.add_cleanup(self.mock_server.stop) config = { 'web_path': 'api/v1/apposit/sms', 'web_port': 0, 'credentials': { '8123': { 'username': '******', 'password': '******', 'service_id': 'service-id-1', }, '8124': { 'username': '******', 'password': '******', 'service_id': 'service-id-2', } }, 'outbound_url': self.mock_server.url, } self.tx_helper = self.add_helper( TransportHelper( AppositTransport, transport_addr='8123', mobile_addr='251911223344')) self.transport = yield self.tx_helper.get_transport(config) self.transport_url = self.transport.get_transport_url() self.web_path = config['web_path'] def send_full_inbound_request(self, **params): return http_request_full( '%s%s' % (self.transport_url, self.web_path), data=urlencode(params), method='POST', headers={'Content-Type': self.transport.CONTENT_TYPE}) def send_inbound_request(self, **kwargs): params = { 'from': '251911223344', 'to': '8123', 'channel': 'SMS', 'content': 'never odd or even', 'isTest': 'true', } params.update(kwargs) return self.send_full_inbound_request(**params) def handle_inbound_request(self, request): self.outbound_requests.put(request) request.setResponseCode(self.mock_server_response_code) return self.mock_server_response def set_mock_server_response(self, code=http.OK, body=''): self.mock_server_response_code = code self.mock_server_response = body def assert_outbound_request(self, request, **kwargs): expected_args = { 'username': '******', 'password': '******', 'serviceId': 'service-id-1', 'fromAddress': '8123', 'toAddress': '251911223344', 'content': 'so many dynamos', 'channel': 'SMS', } expected_args.update(kwargs) self.assertEqual(request.path, '/') self.assertEqual(request.method, 'POST') self.assertEqual(dict((k, [v]) for k, v in expected_args.iteritems()), request.args) self.assertEqual(request.getHeader('Content-Type'), self.transport.CONTENT_TYPE) def assert_message_fields(self, msg, **kwargs): fields = { 'transport_name': self.tx_helper.transport_name, 'transport_type': 'sms', 'from_addr': '251911223344', 'to_addr': '8123', 'content': 'so many dynamos', 'provider': 'apposit', 'transport_metadata': {'apposit': {'isTest': 'true'}}, } fields.update(kwargs) for field_name, expected_value in fields.iteritems(): self.assertEqual(msg[field_name], expected_value) def assert_ack(self, ack, msg): self.assertEqual(ack.payload['event_type'], 'ack') self.assertEqual(ack.payload['user_message_id'], msg['message_id']) self.assertEqual(ack.payload['sent_message_id'], msg['message_id']) def assert_nack(self, nack, msg, reason): self.assertEqual(nack.payload['event_type'], 'nack') self.assertEqual(nack.payload['user_message_id'], msg['message_id']) self.assertEqual(nack.payload['nack_reason'], reason) @inlineCallbacks def test_inbound(self): response = yield self.send_inbound_request(**{ 'from': '251911223344', 'to': '8123', 'content': 'so many dynamos', 'channel': 'SMS', 'isTest': 'true', }) [msg] = self.tx_helper.get_dispatched_inbound() self.assert_message_fields(msg, transport_name=self.tx_helper.transport_name, transport_type='sms', from_addr='251911223344', to_addr='8123', content='so many dynamos', provider='apposit', transport_metadata={'apposit': {'isTest': 'true'}}) self.assertEqual(response.code, http.OK) self.assertEqual(json.loads(response.delivered_body), {'message_id': msg['message_id']}) @inlineCallbacks def test_outbound(self): msg = yield self.tx_helper.make_dispatch_outbound('racecar') request = yield self.outbound_requests.get() self.assert_outbound_request(request, **{ 'username': '******', 'password': '******', 'serviceId': 'service-id-1', 'content': 'racecar', 'fromAddress': '8123', 'toAddress': '251911223344', 'channel': 'SMS' }) [ack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assert_ack(ack, msg) @inlineCallbacks def test_inbound_requests_for_non_ascii_content(self): response = yield self.send_inbound_request( content=u'Hliðskjálf'.encode('UTF-8')) [msg] = self.tx_helper.get_dispatched_inbound() self.assert_message_fields(msg, content=u'Hliðskjálf') self.assertEqual(response.code, http.OK) self.assertEqual(json.loads(response.delivered_body), {'message_id': msg['message_id']}) @inlineCallbacks def test_inbound_requests_for_unsupported_channel(self): response = yield self.send_full_inbound_request(**{ 'from': '251911223344', 'to': '8123', 'channel': 'steven', 'content': 'never odd or even', 'isTest': 'false', }) self.assertEqual(response.code, 400) self.assertEqual(json.loads(response.delivered_body), {'unsupported_channel': 'steven'}) @inlineCallbacks def test_inbound_requests_for_unexpected_param(self): response = yield self.send_full_inbound_request(**{ 'from': '251911223344', 'to': '8123', 'channel': 'SMS', 'steven': 'its a trap', 'content': 'never odd or even', 'isTest': 'false', }) self.assertEqual(response.code, 400) self.assertEqual(json.loads(response.delivered_body), {'unexpected_parameter': ['steven']}) @inlineCallbacks def test_inbound_requests_for_missing_param(self): response = yield self.send_full_inbound_request(**{ 'from': '251911223344', 'to': '8123', 'content': 'never odd or even', 'isTest': 'false', }) self.assertEqual(response.code, 400) self.assertEqual(json.loads(response.delivered_body), {'missing_parameter': ['channel']}) @inlineCallbacks def test_outbound_request_credential_selection(self): msg1 = yield self.tx_helper.make_dispatch_outbound( 'so many dynamos', from_addr='8123') request1 = yield self.outbound_requests.get() self.assert_outbound_request(request1, fromAddress='8123', username='******', password='******', serviceId='service-id-1') msg2 = yield self.tx_helper.make_dispatch_outbound( 'so many dynamos', from_addr='8124') request2 = yield self.outbound_requests.get() self.assert_outbound_request(request2, fromAddress='8124', username='******', password='******', serviceId='service-id-2') [ack1, ack2] = yield self.tx_helper.wait_for_dispatched_events(2) self.assert_ack(ack1, msg1) self.assert_ack(ack2, msg2) @inlineCallbacks def test_outbound_requests_for_non_ascii_content(self): msg = yield self.tx_helper.make_dispatch_outbound(u'Hliðskjálf') request = yield self.outbound_requests.get() self.assert_outbound_request(request, content='Hliðskjálf') [ack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assert_ack(ack, msg) @inlineCallbacks def test_outbound_requests_for_known_error_responses(self): code = '102999' self.set_mock_server_response(http.BAD_REQUEST, code) msg = yield self.tx_helper.make_dispatch_outbound('racecar') [nack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assert_nack(nack, msg, "(%s) %s" % ( code, self.transport.KNOWN_ERROR_RESPONSE_CODES[code])) @inlineCallbacks def test_outbound_requests_for_unknown_error_responses(self): code = '103000' self.set_mock_server_response(http.BAD_REQUEST, code) msg = yield self.tx_helper.make_dispatch_outbound("so many dynamos") [nack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assert_nack( nack, msg, self.transport.UNKNOWN_RESPONSE_CODE_ERROR % code) @inlineCallbacks def test_outbound_requests_for_unsupported_transport_types(self): transport_type = 'steven' msg = yield self.tx_helper.make_dispatch_outbound( "so many dynamos", transport_type=transport_type) [nack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assert_nack(nack, msg, self.transport.UNSUPPORTED_TRANSPORT_TYPE_ERROR % transport_type)
class TestMxitTransport(VumiTestCase): @inlineCallbacks def setUp(self): self.mock_http = MockHttpServer(self.handle_request) self.mock_request_queue = DeferredQueue() yield self.mock_http.start() self.addCleanup(self.mock_http.stop) config = { "web_port": 0, "web_path": "/api/v1/mxit/mobiportal/", "client_id": "client_id", "client_secret": "client_secret", "api_send_url": self.mock_http.url, "api_auth_url": self.mock_http.url, } self.sample_loc_str = "cc,cn,sc,sn,cc,c,noi,cfb,ci" self.sample_profile_str = "lc,cc,dob,gender,tariff" self.sample_html_str = "<&>" self.sample_req_headers = { "X-Device-User-Agent": "ua", "X-Mxit-Contact": "contact", "X-Mxit-USERID-R": "user-id", "X-Mxit-Nick": "nick", "X-Mxit-Location": self.sample_loc_str, "X-Mxit-Profile": self.sample_profile_str, "X-Mxit-User-Input": self.sample_html_str, } self.sample_menu_resp = "\n".join(["Hello!", "1. option 1", "2. option 2", "3. option 3"]) # same as above but the o's are replaced with # http://www.fileformat.info/info/unicode/char/f8/index.htm slashed_o = "\xc3\xb8" self.sample_unicode_menu_resp = unicode(self.sample_menu_resp.replace("o", slashed_o), "utf-8") self.tx_helper = self.add_helper(TransportHelper(MxitTransport)) self.transport = yield self.tx_helper.get_transport(config) # NOTE: priming redis with an access token self.transport.redis.set(self.transport.access_token_key, "foo") self.url = self.transport.get_transport_url(config["web_path"]) def handle_request(self, request): self.mock_request_queue.put(request) return NOT_DONE_YET def test_is_mxit_request(self): req = Request(None, True) self.assertFalse(self.transport.is_mxit_request(req)) req.requestHeaders.addRawHeader("X-Mxit-Contact", "foo") self.assertTrue(self.transport.is_mxit_request(req)) def test_noop(self): self.assertEqual(self.transport.noop("foo"), "foo") def test_parse_location(self): self.assertEqual( self.transport.parse_location(self.sample_loc_str), { "country_code": "cc", "country_name": "cn", "subdivision_code": "sc", "subdivision_name": "sn", "city_code": "cc", "city": "c", "network_operator_id": "noi", "client_features_bitset": "cfb", "cell_id": "ci", }, ) def test_parse_profile(self): self.assertEqual( self.transport.parse_profile(self.sample_profile_str), { "country_code": "cc", "date_of_birth": "dob", "gender": "gender", "language_code": "lc", "tariff_plan": "tariff", }, ) def test_html_decode(self): self.assertEqual(self.transport.html_decode(self.sample_html_str), "<&>") def test_get_request_data(self): req = Request(None, True) headers = req.requestHeaders for key, value in self.sample_req_headers.items(): headers.addRawHeader(key, value) data = self.transport.get_request_data(req) self.assertEqual( data, { "X-Device-User-Agent": "ua", "X-Mxit-Contact": "contact", "X-Mxit-Location": { "cell_id": "ci", "city": "c", "city_code": "cc", "client_features_bitset": "cfb", "country_code": "cc", "country_name": "cn", "network_operator_id": "noi", "subdivision_code": "sc", "subdivision_name": "sn", }, "X-Mxit-Nick": "nick", "X-Mxit-Profile": { "country_code": "cc", "date_of_birth": "dob", "gender": "gender", "language_code": "lc", "tariff_plan": "tariff", }, "X-Mxit-USERID-R": "user-id", "X-Mxit-User-Input": u"<&>", }, ) def test_get_request_content_from_header(self): req = Request(None, True) req.requestHeaders.addRawHeader("X-Mxit-User-Input", "foo") self.assertEqual(self.transport.get_request_content(req), "foo") def test_get_quote_plus_request_content_from_header(self): req = Request(None, True) req.requestHeaders.addRawHeader("X-Mxit-User-Input", "foo+bar") self.assertEqual(self.transport.get_request_content(req), "foo bar") def test_get_quoted_request_content_from_header(self): req = Request(None, True) req.requestHeaders.addRawHeader("X-Mxit-User-Input", "foo%20bar") self.assertEqual(self.transport.get_request_content(req), "foo bar") def test_get_request_content_from_args(self): req = Request(None, True) req.args = {"input": ["bar"]} self.assertEqual(self.transport.get_request_content(req), "bar") def test_get_request_content_when_missing(self): req = Request(None, True) self.assertEqual(self.transport.get_request_content(req), None) @inlineCallbacks def test_invalid_request(self): resp = yield http_request_full(self.url) self.assertEqual(resp.code, BAD_REQUEST) @inlineCallbacks def test_request(self): resp_d = http_request_full(self.url, headers=self.sample_req_headers) [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.tx_helper.make_dispatch_reply(msg, self.sample_menu_resp) resp = yield resp_d self.assertTrue("1. option 1" in resp.delivered_body) self.assertTrue("2. option 2" in resp.delivered_body) self.assertTrue("3. option 3" in resp.delivered_body) self.assertTrue("?input=1" in resp.delivered_body) self.assertTrue("?input=2" in resp.delivered_body) self.assertTrue("?input=3" in resp.delivered_body) def test_response_parser(self): header, items = ResponseParser.parse(self.sample_menu_resp) self.assertEqual(header, "Hello!") self.assertEqual(items, [("1", "option 1"), ("2", "option 2"), ("3", "option 3")]) header, items = ResponseParser.parse("foo!") self.assertEqual(header, "foo!") self.assertEqual(items, []) @inlineCallbacks def test_unicode_rendering(self): resp_d = http_request_full(self.url, headers=self.sample_req_headers) [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.tx_helper.make_dispatch_reply(msg, self.sample_unicode_menu_resp) resp = yield resp_d self.assertTrue("Hell\xc3\xb8" in resp.delivered_body) self.assertTrue("\xc3\xb8pti\xc3\xb8n 1" in resp.delivered_body) @inlineCallbacks def test_outbound_that_is_not_a_reply(self): d = self.tx_helper.make_dispatch_outbound(content="Send!", to_addr="mxit-1", from_addr="mxit-2") req = yield self.mock_request_queue.get() body = json.load(req.content) self.assertEqual( body, {"Body": "Send!", "To": "mxit-1", "From": "mxit-2", "ContainsMarkup": "true", "Spool": "true"} ) [auth] = req.requestHeaders.getRawHeaders("Authorization") # primed access token self.assertEqual(auth, "Bearer foo") req.finish() yield d @inlineCallbacks def test_getting_access_token(self): transport = self.transport redis = transport.redis # clear primed value yield redis.delete(transport.access_token_key) d = transport.get_access_token() req = yield self.mock_request_queue.get() [auth] = req.requestHeaders.getRawHeaders("Authorization") self.assertEqual(auth, "Basic %s" % (base64.b64encode("client_id:client_secret"))) self.assertEqual( ["grant_type=client_credentials", "scope=message%2Fsend"], sorted(req.content.read().split("&")) ) req.write(json.dumps({"access_token": "access_token", "expires_in": "10"})) req.finish() access_token = yield d self.assertEqual(access_token, "access_token") self.assertFalse(isinstance(access_token, unicode)) ttl = yield redis.ttl(transport.access_token_key) self.assertTrue(0 < ttl <= (transport.access_token_auto_decay * 10))
class TestMediafoneTransport(TransportTestCase): timeout = 5 transport_name = 'test_mediafone_transport' transport_class = MediafoneTransport @inlineCallbacks def setUp(self): super(TestMediafoneTransport, self).setUp() self.mediafone_calls = DeferredQueue() self.mock_mediafone = MockHttpServer(self.handle_request) yield self.mock_mediafone.start() self.config = { 'transport_name': self.transport_name, 'web_path': "foo", 'web_port': 0, 'username': '******', 'password': '******', 'outbound_url': self.mock_mediafone.url, } self.transport = yield self.get_transport(self.config) self.transport_url = self.transport.get_transport_url() @inlineCallbacks def tearDown(self): yield self.mock_mediafone.stop() yield super(TestMediafoneTransport, self).tearDown() def handle_request(self, request): self.mediafone_calls.put(request) return '' def mkurl(self, content, from_addr="2371234567", **kw): params = { 'to': '12345', 'from': from_addr, 'sms': content, } 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='GET') [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_outbound(self): yield self.dispatch(self.mkmsg_out(to_addr="2371234567")) req = yield self.mediafone_calls.get() self.assertEqual(req.path, '/') self.assertEqual(req.method, 'GET') self.assertEqual( { 'username': ['user'], 'phone': ['2371234567'], 'password': ['pass'], 'msg': ['hello world'], }, req.args) @inlineCallbacks def test_handle_non_ascii_input(self): url = self.mkurl(u"öæł".encode("utf-8")) response = yield http_request(url, '', method='GET') [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='GET') self.assertEqual(400, response.code) self.assertEqual(json.loads(response.delivered_body), {'unexpected_parameter': ['foo']}) @inlineCallbacks def test_missing_parameters(self): url = self.mkurl_raw(to='12345', sms='hello') response = yield http_request_full(url, '', method='GET') self.assertEqual(400, response.code) self.assertEqual(json.loads(response.delivered_body), {'missing_parameter': ['from']})
class TestVumiApiWorkerBase(VumiTestCase): @inlineCallbacks def setUp(self): self.app_helper = yield self.add_helper( ApplicationHelper(VumiApiWorker)) @inlineCallbacks def start_app_worker(self, config_overrides={}): # Mock server to test HTTP posting of inbound messages & events self.mock_push_server = MockHttpServer(self.handle_request) yield self.mock_push_server.start() self.add_cleanup(self.mock_push_server.stop) self.push_calls = DeferredQueue() self.config = { 'conversation_key': 'key_conversation', 'push_message_url': self.get_message_url(), 'push_event_url': self.get_event_url(), 'health_path': '/health/', 'web_path': '/foo', 'web_port': 0, 'api_tokens': [{ 'account': 'account_key', 'conversation': 'key_conversation', 'tokens': ['token-1', 'token-2', 'token-3'], }, ] } self.config.update(config_overrides) self.app = yield self.app_helper.get_application(self.config) self.conversation = self.config['conversation_key'] self.addr = self.app.webserver.getHost() self.url = 'http://%s:%s%s' % ( self.addr.host, self.addr.port, self.config['web_path']) self.auth_headers = { 'Authorization': ['Basic ' + base64.b64encode('%s:%s' % ( 'account_key', 'token-1'))], } def get_message_url(self): return self.mock_push_server.url def get_event_url(self): return self.mock_push_server.url def handle_request(self, request): self.push_calls.put(request) return NOT_DONE_YET def assert_bad_request(self, response, reason): self.assertEqual(response.code, http.BAD_REQUEST) self.assertEqual( response.headers.getRawHeaders('content-type'), ['application/json; charset=utf-8']) data = json.loads(response.delivered_body) self.assertEqual(data, { "success": False, "reason": reason, }) def _patch_http_request_full(self, exception_class): from vumi_http_api import vumi_api def raiser(*args, **kw): raise exception_class() self.patch(vumi_api, 'http_request_full', raiser)
class TestAcksCellulantSmsTransport(VumiTestCase): @inlineCallbacks def setUp(self): self.cellulant_sms_calls = DeferredQueue() self.mock_cellulant_sms = MockHttpServer(self.handle_request) self._mock_response = '' yield self.mock_cellulant_sms.start() self.add_cleanup(self.mock_cellulant_sms.stop) self.config = { 'web_path': "foo", 'web_port': 0, 'credentials': { '2371234567': { 'username': '******', 'password': '******', }, '9292': { 'username': '******', 'password': '******', } }, 'outbound_url': self.mock_cellulant_sms.url, 'validation_mode': 'permissive', } self.tx_helper = self.add_helper( TransportHelper(CellulantSmsTransport)) self.transport = yield self.tx_helper.get_transport(self.config) self.transport_url = self.transport.get_transport_url() def mock_response(self, response): self._mock_response = response def handle_request(self, request): self.cellulant_sms_calls.put(request) return self._mock_response @inlineCallbacks def mock_event(self, msg, nr_events): self.mock_response(msg) yield self.tx_helper.make_dispatch_outbound( "foo", to_addr='2371234567', message_id='id_%s' % (msg,)) yield self.cellulant_sms_calls.get() events = yield self.tx_helper.wait_for_dispatched_events(nr_events) returnValue(events) @inlineCallbacks def test_nack_param_error_E0(self): [nack] = yield self.mock_event('E0', 1) self.assertEqual(nack['event_type'], 'nack') self.assertEqual(nack['user_message_id'], 'id_E0') self.assertEqual(nack['nack_reason'], self.transport.KNOWN_ERROR_RESPONSE_CODES['E0']) @inlineCallbacks def test_nack_login_error_E1(self): [nack] = yield self.mock_event('E1', 1) self.assertEqual(nack['event_type'], 'nack') self.assertEqual(nack['user_message_id'], 'id_E1') self.assertEqual(nack['nack_reason'], self.transport.KNOWN_ERROR_RESPONSE_CODES['E1']) @inlineCallbacks def test_nack_credits_error_E2(self): [nack] = yield self.mock_event('E2', 1) self.assertEqual(nack['event_type'], 'nack') self.assertEqual(nack['user_message_id'], 'id_E2') self.assertEqual(nack['nack_reason'], self.transport.KNOWN_ERROR_RESPONSE_CODES['E2']) @inlineCallbacks def test_nack_delivery_failed_1005(self): [nack] = yield self.mock_event('1005', 1) self.assertEqual(nack['event_type'], 'nack') self.assertEqual(nack['user_message_id'], 'id_1005') self.assertEqual(nack['nack_reason'], self.transport.KNOWN_ERROR_RESPONSE_CODES['1005']) @inlineCallbacks def test_unknown_response(self): [nack] = yield self.mock_event('something_unexpected', 1) self.assertEqual(nack['event_type'], 'nack') self.assertEqual(nack['user_message_id'], 'id_something_unexpected') self.assertEqual(nack['nack_reason'], 'Unknown response code: something_unexpected') @inlineCallbacks def test_ack_success(self): [event] = yield self.mock_event('1', 1) self.assertEqual(event['event_type'], 'ack') self.assertEqual(event['user_message_id'], 'id_1')
class TestIntegratTransport(VumiTestCase): @inlineCallbacks def setUp(self): self.integrat_calls = DeferredQueue() self.mock_integrat = MockHttpServer(self.handle_request) self.add_cleanup(self.mock_integrat.stop) yield self.mock_integrat.start() config = { 'web_path': "foo", 'web_port': "0", 'url': self.mock_integrat.url, 'username': '******', 'password': '******', } self.tx_helper = self.add_helper(TransportHelper(IntegratTransport)) self.transport = yield self.tx_helper.get_transport(config) addr = self.transport.web_resource.getHost() self.transport_url = "http://%s:%s/" % (addr.host, addr.port) self.higate_response = '<Response status_code="0"/>' def handle_request(self, request): # The content attr will have been set to None by the time we read this. request.content_body = request.content.getvalue() self.integrat_calls.put(request) return self.higate_response @inlineCallbacks def test_health(self): result = yield http_request(self.transport_url + "health", "", method='GET') self.assertEqual(result, "OK") @inlineCallbacks def test_outbound(self): yield self.tx_helper.make_dispatch_outbound("hi", transport_metadata={ 'session_id': "sess123", }) req = yield self.integrat_calls.get() self.assertEqual(req.path, '/') self.assertEqual(req.method, 'POST') self.assertEqual(req.getHeader('content-type'), 'text/xml; charset=utf-8') self.assertEqual( req.content_body, '<Message><Version Version="1.0" />' '<Request Flags="0" SessionID="sess123"' ' Type="USSReply">' '<UserID Orientation="TR">testuser</UserID>' '<Password>testpass</Password>' '<USSText Type="TEXT">hi</USSText>' '</Request></Message>') @inlineCallbacks def test_outbound_no_content(self): yield self.tx_helper.make_dispatch_outbound(None, transport_metadata={ 'session_id': "sess123", }) req = yield self.integrat_calls.get() self.assertEqual(req.path, '/') self.assertEqual(req.method, 'POST') self.assertEqual(req.getHeader('content-type'), 'text/xml; charset=utf-8') self.assertEqual( req.content_body, '<Message><Version Version="1.0" />' '<Request Flags="0" SessionID="sess123"' ' Type="USSReply">' '<UserID Orientation="TR">testuser</UserID>' '<Password>testpass</Password>' '<USSText Type="TEXT" />' '</Request></Message>') @inlineCallbacks def test_inbound(self): xml = XML_TEMPLATE % { 'ussd_type': 'Request', 'sid': 'sess1234', 'network_sid': "netsid12345", 'msisdn': '27345', 'connstr': '*120*99#', 'text': 'foobar', } yield http_request(self.transport_url + "foo", xml, method='GET') [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg['transport_name'], self.tx_helper.transport_name) self.assertEqual(msg['transport_type'], "ussd") self.assertEqual(msg['transport_metadata'], {"session_id": "sess1234"}) self.assertEqual(msg['session_event'], TransportUserMessage.SESSION_RESUME) self.assertEqual(msg['from_addr'], '27345') self.assertEqual(msg['to_addr'], '*120*99#') self.assertEqual(msg['content'], 'foobar') @inlineCallbacks def test_inbound_non_ascii(self): xml = (XML_TEMPLATE % { 'ussd_type': 'Request', 'sid': 'sess1234', 'network_sid': "netsid12345", 'msisdn': '27345', 'connstr': '*120*99#', 'text': u'öæł', }).encode("utf-8") yield http_request(self.transport_url + "foo", xml, method='GET') [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg['content'], u'öæł') @inlineCallbacks def test_nack(self): self.higate_response = """ <Response status_code="-1"> <Data name="method_error"> <field name="error_code" value="-1"/> <field name="reason" value="Expecting POST, not GET"/> </Data> </Response>""".strip() msg = yield self.tx_helper.make_dispatch_outbound( "hi", transport_metadata={'session_id': "sess123"}) yield self.integrat_calls.get() [nack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assertEqual(nack['user_message_id'], msg['message_id']) self.assertEqual(nack['sent_message_id'], msg['message_id']) self.assertEqual(nack['nack_reason'], 'error_code: -1, reason: Expecting POST, not GET')
class TestIntegratTransport(TestCase): timeout = 5 @inlineCallbacks def setUp(self): self.integrat_calls = DeferredQueue() self.mock_integrat = MockHttpServer(self.handle_request) yield self.mock_integrat.start() config = { 'transport_name': 'testgrat', 'web_path': "foo", 'web_port': "0", 'url': self.mock_integrat.url, 'username': '******', 'password': '******', } self.worker = get_stubbed_worker(IntegratTransport, config) self.broker = self.worker._amqp_client.broker yield self.worker.startWorker() addr = self.worker.web_resource.getHost() self.worker_url = "http://%s:%s/" % (addr.host, addr.port) @inlineCallbacks def tearDown(self): yield self.worker.stopWorker() yield self.mock_integrat.stop() def handle_request(self, request): self.integrat_calls.put(request) return '' @inlineCallbacks def test_health(self): result = yield http_request(self.worker_url + "health", "", method='GET') self.assertEqual(result, "OK") @inlineCallbacks def test_outbound(self): msg = TransportUserMessage(to_addr="12345", from_addr="56789", transport_name="testgrat", transport_type="ussd", transport_metadata={ 'session_id': "sess123", }, ) self.broker.publish_message("vumi", "testgrat.outbound", msg) req = yield self.integrat_calls.get() self.assertEqual(req.path, '/') self.assertEqual(req.method, 'POST') self.assertEqual(req.getHeader('content-type'), 'text/xml; charset=utf-8') self.assertEqual(req.content.getvalue(), '<Message><Version Version="1.0" />' '<Request Flags="0" SessionID="sess123"' ' Type="USSReply">' '<UserID Orientation="TR">testuser</UserID>' '<Password>testpass</Password>' '<USSText Type="TEXT" />' '</Request></Message>') @inlineCallbacks def test_inbound(self): xml = XML_TEMPLATE % { 'ussd_type': 'Request', 'sid': 'sess1234', 'network_sid': "netsid12345", 'msisdn': '27345', 'connstr': '*120*99#', 'text': 'foobar', } yield http_request(self.worker_url + "foo", xml, method='GET') msg, = yield self.broker.wait_messages("vumi", "testgrat.inbound", 1) payload = msg.payload self.assertEqual(payload['transport_name'], "testgrat") self.assertEqual(payload['transport_type'], "ussd") self.assertEqual(payload['transport_metadata'], {"session_id": "sess1234"}) self.assertEqual(payload['session_event'], TransportUserMessage.SESSION_RESUME) self.assertEqual(payload['from_addr'], '27345') self.assertEqual(payload['to_addr'], '*120*99#') self.assertEqual(payload['content'], 'foobar') @inlineCallbacks def test_inbound_non_ascii(self): xml = (XML_TEMPLATE % { 'ussd_type': 'Request', 'sid': 'sess1234', 'network_sid': "netsid12345", 'msisdn': '27345', 'connstr': '*120*99#', 'text': u'öæł', }).encode("utf-8") yield http_request(self.worker_url + "foo", xml, method='GET') msg, = yield self.broker.wait_messages("vumi", "testgrat.inbound", 1) payload = msg.payload self.assertEqual(payload['content'], u'öæł')
class TestMediaEdgeGSMTransport(VumiTestCase): @inlineCallbacks def setUp(self): self.mediaedgegsm_calls = DeferredQueue() self.mock_mediaedgegsm = MockHttpServer(self.handle_request) self.add_cleanup(self.mock_mediaedgegsm.stop) yield self.mock_mediaedgegsm.start() self.config = { 'web_path': "foo", 'web_port': 0, 'username': '******', 'password': '******', 'outbound_url': self.mock_mediaedgegsm.url, 'outbound_username': '******', 'outbound_password': '******', 'operator_mappings': { '417': { '417912': 'VODA', '417913': 'TIGO', '417914': 'UNKNOWN', } } } self.tx_helper = self.add_helper( TransportHelper(MediaEdgeGSMTransport)) self.transport = yield self.tx_helper.get_transport(self.config) self.transport_url = self.transport.get_transport_url() self.mediaedgegsm_response = '' self.mediaedgegsm_response_code = http.OK def handle_request(self, request): self.mediaedgegsm_calls.put(request) request.setResponseCode(self.mediaedgegsm_response_code) return self.mediaedgegsm_response def mkurl(self, content, from_addr="2371234567", **kw): params = { 'ServiceNumber': '12345', 'PhoneNumber': from_addr, 'SMSBODY': content, 'USN': 'user', 'PWD': 'pass', 'Operator': 'foo', } 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') deferred = http_request(url, '', method='GET') [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg['transport_name'], self.tx_helper.transport_name) self.assertEqual(msg['to_addr'], "12345") self.assertEqual(msg['from_addr'], "2371234567") self.assertEqual(msg['content'], "hello") yield self.tx_helper.make_dispatch_reply(msg, 'message received') response = yield deferred self.assertEqual(response, 'message received') @inlineCallbacks def test_outbound(self): msisdns = ['+41791200000', '+41791300000', '+41791400000'] operators = ['VODA', 'TIGO', 'UNKNOWN'] sent_messages = [] for msisdn in msisdns: msg = yield self.tx_helper.make_dispatch_outbound( "outbound", to_addr=msisdn) sent_messages.append(msg) req1 = yield self.mediaedgegsm_calls.get() req2 = yield self.mediaedgegsm_calls.get() req3 = yield self.mediaedgegsm_calls.get() requests = [req1, req2, req3] for req in requests: self.assertEqual(req.path, '/') self.assertEqual(req.method, 'GET') collections = zip(msisdns, operators, sent_messages, requests) for msisdn, operator, msg, req in collections: self.assertEqual({ 'USN': ['username'], 'PWD': ['password'], 'SmsID': [msg['message_id']], 'PhoneNumber': [msisdn.lstrip('+')], 'Operator': [operator], 'SmsBody': [msg['content']], }, req.args) @inlineCallbacks def test_nack(self): self.mediaedgegsm_response_code = http.NOT_FOUND self.mediaedgegsm_response = 'Not Found' msg = yield self.tx_helper.make_dispatch_outbound( "outbound", to_addr='+41791200000') yield self.mediaedgegsm_calls.get() [nack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assertEqual(nack['user_message_id'], msg['message_id']) self.assertEqual(nack['sent_message_id'], msg['message_id']) self.assertEqual(nack['nack_reason'], 'Unexpected response code: 404') @inlineCallbacks def test_bad_parameter(self): url = self.mkurl('hello', foo='bar') response = yield http_request_full(url, '', method='GET') self.assertEqual(400, response.code) self.assertEqual(json.loads(response.delivered_body), {'unexpected_parameter': ['foo']}) @inlineCallbacks def test_missing_parameters(self): url = self.mkurl_raw(ServiceNumber='12345', SMSBODY='hello', USN='user', PWD='pass', Operator='foo') response = yield http_request_full(url, '', method='GET') self.assertEqual(400, response.code) self.assertEqual(json.loads(response.delivered_body), {'missing_parameter': ['PhoneNumber']}) @inlineCallbacks def test_invalid_credentials(self): url = self.mkurl_raw(ServiceNumber='12345', SMSBODY='hello', USN='something', PWD='wrong', Operator='foo', PhoneNumber='1234') response = yield http_request_full(url, '', method='GET') self.assertEqual(400, response.code) self.assertEqual(json.loads(response.delivered_body), {'credentials': 'invalid'}) @inlineCallbacks def test_handle_non_ascii_input(self): url = self.mkurl(u"öæł".encode("utf-8")) deferred = http_request_full(url, '', method='GET') [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg['transport_name'], self.tx_helper.transport_name) self.assertEqual(msg['to_addr'], "12345") self.assertEqual(msg['from_addr'], "2371234567") self.assertEqual(msg['content'], u"öæł") yield self.tx_helper.make_dispatch_reply(msg, u'Zoë says hi') response = yield deferred self.assertEqual(response.headers.getRawHeaders('Content-Type'), ['text/plain; charset=utf-8']) self.assertEqual(response.delivered_body, u'Zoë says hi'.encode('utf-8'))
class TestStreamingHTTPWorker(VumiTestCase): @inlineCallbacks def setUp(self): self.app_helper = self.add_helper(AppWorkerHelper(StreamingHTTPWorker)) self.config = { 'health_path': '/health/', 'web_path': '/foo', 'web_port': 0, 'metrics_prefix': 'metrics_prefix.', } self.app = yield self.app_helper.get_app_worker(self.config) self.addr = self.app.webserver.getHost() self.url = 'http://%s:%s%s' % ( self.addr.host, self.addr.port, self.config['web_path']) conv_config = { 'http_api': { 'api_tokens': [ 'token-1', 'token-2', 'token-3', ], 'metric_store': 'metric_store', } } conversation = yield self.app_helper.create_conversation( config=conv_config) yield self.app_helper.start_conversation(conversation) self.conversation = yield self.app_helper.get_conversation( conversation.key) self.auth_headers = { 'Authorization': ['Basic ' + base64.b64encode('%s:%s' % ( conversation.user_account.key, 'token-1'))], } self.client = StreamingClient() # Mock server to test HTTP posting of inbound messages & events self.mock_push_server = MockHttpServer(self.handle_request) yield self.mock_push_server.start() self.add_cleanup(self.mock_push_server.stop) self.push_calls = DeferredQueue() self._setup_wait_for_request() self.add_cleanup(self._wait_for_requests) def _setup_wait_for_request(self): # Hackery to wait for the request to finish self._req_state = { 'queue': DeferredQueue(), 'expected': 0, } orig_track = StreamingConversationResource.track_request orig_release = StreamingConversationResource.release_request def track_wrapper(*args, **kw): self._req_state['expected'] += 1 return orig_track(*args, **kw) def release_wrapper(*args, **kw): return orig_release(*args, **kw).addCallback( self._req_state['queue'].put) self.patch( StreamingConversationResource, 'track_request', track_wrapper) self.patch( StreamingConversationResource, 'release_request', release_wrapper) @inlineCallbacks def _wait_for_requests(self): while self._req_state['expected'] > 0: yield self._req_state['queue'].get() self._req_state['expected'] -= 1 def handle_request(self, request): self.push_calls.put(request) return NOT_DONE_YET @inlineCallbacks def pull_message(self, count=1): url = '%s/%s/messages.json' % (self.url, self.conversation.key) messages = DeferredQueue() errors = DeferredQueue() receiver = self.client.stream( TransportUserMessage, messages.put, errors.put, url, Headers(self.auth_headers)) received_messages = [] for msg_id in range(count): yield self.app_helper.make_dispatch_inbound( 'in %s' % (msg_id,), message_id=str(msg_id), conv=self.conversation) recv_msg = yield messages.get() received_messages.append(recv_msg) receiver.disconnect() returnValue((receiver, received_messages)) def assert_bad_request(self, response, reason): self.assertEqual(response.code, http.BAD_REQUEST) data = json.loads(response.delivered_body) self.assertEqual(data, { "success": False, "reason": reason, }) @inlineCallbacks def test_proxy_buffering_headers_off(self): # This is the default, but we patch it anyway to make sure we're # testing the right thing should the default change. self.patch(StreamResourceMixin, 'proxy_buffering', False) receiver, received_messages = yield self.pull_message() headers = receiver._response.headers self.assertEqual(headers.getRawHeaders('x-accel-buffering'), ['no']) @inlineCallbacks def test_proxy_buffering_headers_on(self): self.patch(StreamResourceMixin, 'proxy_buffering', True) receiver, received_messages = yield self.pull_message() headers = receiver._response.headers self.assertEqual(headers.getRawHeaders('x-accel-buffering'), ['yes']) @inlineCallbacks def test_content_type(self): receiver, received_messages = yield self.pull_message() headers = receiver._response.headers self.assertEqual( headers.getRawHeaders('content-type'), ['application/json; charset=utf-8']) @inlineCallbacks def test_messages_stream(self): url = '%s/%s/messages.json' % (self.url, self.conversation.key) messages = DeferredQueue() errors = DeferredQueue() receiver = self.client.stream( TransportUserMessage, messages.put, errors.put, url, Headers(self.auth_headers)) msg1 = yield self.app_helper.make_dispatch_inbound( 'in 1', message_id='1', conv=self.conversation) msg2 = yield self.app_helper.make_dispatch_inbound( 'in 2', message_id='2', conv=self.conversation) rm1 = yield messages.get() rm2 = yield messages.get() receiver.disconnect() # Sometimes messages arrive out of order if we're hitting real redis. rm1, rm2 = sorted([rm1, rm2], key=lambda m: m['message_id']) self.assertEqual(msg1['message_id'], rm1['message_id']) self.assertEqual(msg2['message_id'], rm2['message_id']) self.assertEqual(errors.size, None) @inlineCallbacks def test_events_stream(self): url = '%s/%s/events.json' % (self.url, self.conversation.key) events = DeferredQueue() errors = DeferredQueue() receiver = yield self.client.stream(TransportEvent, events.put, events.put, url, Headers(self.auth_headers)) msg1 = yield self.app_helper.make_stored_outbound( self.conversation, 'out 1', message_id='1') ack1 = yield self.app_helper.make_dispatch_ack( msg1, conv=self.conversation) msg2 = yield self.app_helper.make_stored_outbound( self.conversation, 'out 2', message_id='2') ack2 = yield self.app_helper.make_dispatch_ack( msg2, conv=self.conversation) ra1 = yield events.get() ra2 = yield events.get() receiver.disconnect() self.assertEqual(ack1['event_id'], ra1['event_id']) self.assertEqual(ack2['event_id'], ra2['event_id']) self.assertEqual(errors.size, None) @inlineCallbacks def test_missing_auth(self): url = '%s/%s/messages.json' % (self.url, self.conversation.key) queue = DeferredQueue() receiver = self.client.stream( TransportUserMessage, queue.put, queue.put, url) response = yield receiver.get_response() self.assertEqual(response.code, http.UNAUTHORIZED) self.assertEqual(response.headers.getRawHeaders('www-authenticate'), [ 'basic realm="Conversation Realm"']) @inlineCallbacks def test_invalid_auth(self): url = '%s/%s/messages.json' % (self.url, self.conversation.key) queue = DeferredQueue() headers = Headers({ 'Authorization': ['Basic %s' % (base64.b64encode('foo:bar'),)], }) receiver = self.client.stream( TransportUserMessage, queue.put, queue.put, url, headers) response = yield receiver.get_response() self.assertEqual(response.code, http.UNAUTHORIZED) self.assertEqual(response.headers.getRawHeaders('www-authenticate'), [ 'basic realm="Conversation Realm"']) @inlineCallbacks def test_send_to(self): msg = { 'to_addr': '+2345', 'content': 'foo', 'message_id': 'evil_id', } # TaggingMiddleware.add_tag_to_msg(msg, self.tag) url = '%s/%s/messages.json' % (self.url, self.conversation.key) response = yield http_request_full(url, json.dumps(msg), self.auth_headers, method='PUT') self.assertEqual(response.code, http.OK) put_msg = json.loads(response.delivered_body) [sent_msg] = self.app_helper.get_dispatched_outbound() self.assertEqual(sent_msg['to_addr'], sent_msg['to_addr']) self.assertEqual(sent_msg['helper_metadata'], { 'go': { 'conversation_key': self.conversation.key, 'conversation_type': 'http_api', 'user_account': self.conversation.user_account.key, }, }) # We do not respect the message_id that's been given. self.assertNotEqual(sent_msg['message_id'], msg['message_id']) self.assertEqual(sent_msg['message_id'], put_msg['message_id']) self.assertEqual(sent_msg['to_addr'], msg['to_addr']) self.assertEqual(sent_msg['from_addr'], None) @inlineCallbacks def test_in_send_to_with_evil_content(self): msg = { 'content': 0xBAD, 'to_addr': '+1234', } url = '%s/%s/messages.json' % (self.url, self.conversation.key) response = yield http_request_full(url, json.dumps(msg), self.auth_headers, method='PUT') self.assert_bad_request( response, "Invalid or missing value for payload key 'content'") @inlineCallbacks def test_in_send_to_with_evil_to_addr(self): msg = { 'content': 'good', 'to_addr': 1234, } url = '%s/%s/messages.json' % (self.url, self.conversation.key) response = yield http_request_full(url, json.dumps(msg), self.auth_headers, method='PUT') self.assert_bad_request( response, "Invalid or missing value for payload key 'to_addr'") @inlineCallbacks def test_in_reply_to(self): inbound_msg = yield self.app_helper.make_stored_inbound( self.conversation, 'in 1', message_id='1') msg = { 'content': 'foo', 'in_reply_to': inbound_msg['message_id'], } url = '%s/%s/messages.json' % (self.url, self.conversation.key) response = yield http_request_full(url, json.dumps(msg), self.auth_headers, method='PUT') put_msg = json.loads(response.delivered_body) self.assertEqual(response.code, http.OK) [sent_msg] = self.app_helper.get_dispatched_outbound() self.assertEqual(sent_msg['to_addr'], put_msg['to_addr']) self.assertEqual(sent_msg['helper_metadata'], { 'go': { 'conversation_key': self.conversation.key, 'conversation_type': 'http_api', 'user_account': self.conversation.user_account.key, }, }) self.assertEqual(sent_msg['message_id'], put_msg['message_id']) self.assertEqual(sent_msg['session_event'], None) self.assertEqual(sent_msg['to_addr'], inbound_msg['from_addr']) self.assertEqual(sent_msg['from_addr'], '9292') @inlineCallbacks def test_in_reply_to_with_evil_content(self): inbound_msg = yield self.app_helper.make_stored_inbound( self.conversation, 'in 1', message_id='1') msg = { 'content': 0xBAD, 'in_reply_to': inbound_msg['message_id'], } url = '%s/%s/messages.json' % (self.url, self.conversation.key) response = yield http_request_full(url, json.dumps(msg), self.auth_headers, method='PUT') self.assert_bad_request( response, "Invalid or missing value for payload key 'content'") @inlineCallbacks def test_invalid_in_reply_to(self): msg = { 'content': 'foo', 'in_reply_to': '1', # this doesn't exist } url = '%s/%s/messages.json' % (self.url, self.conversation.key) response = yield http_request_full(url, json.dumps(msg), self.auth_headers, method='PUT') self.assert_bad_request(response, 'Invalid in_reply_to value') @inlineCallbacks def test_invalid_in_reply_to_with_missing_conversation_key(self): # create a message with no conversation inbound_msg = self.app_helper.make_inbound('in 1', message_id='msg-1') vumi_api = self.app_helper.vumi_helper.get_vumi_api() yield vumi_api.mdb.add_inbound_message(inbound_msg) msg = { 'content': 'foo', 'in_reply_to': inbound_msg['message_id'], } url = '%s/%s/messages.json' % (self.url, self.conversation.key) with LogCatcher(message='Invalid reply to message <Message .*>' ' which has no conversation key') as lc: response = yield http_request_full(url, json.dumps(msg), self.auth_headers, method='PUT') [error_log] = lc.messages() self.assert_bad_request(response, "Invalid in_reply_to value") self.assertTrue(inbound_msg['message_id'] in error_log) @inlineCallbacks def test_in_reply_to_with_evil_session_event(self): inbound_msg = yield self.app_helper.make_stored_inbound( self.conversation, 'in 1', message_id='1') msg = { 'content': 'foo', 'in_reply_to': inbound_msg['message_id'], 'session_event': 0xBAD5E55104, } url = '%s/%s/messages.json' % (self.url, self.conversation.key) response = yield http_request_full(url, json.dumps(msg), self.auth_headers, method='PUT') self.assert_bad_request( response, "Invalid or missing value for payload key 'session_event'") self.assertEqual(self.app_helper.get_dispatched_outbound(), []) @inlineCallbacks def test_in_reply_to_with_evil_message_id(self): inbound_msg = yield self.app_helper.make_stored_inbound( self.conversation, 'in 1', message_id='1') msg = { 'content': 'foo', 'in_reply_to': inbound_msg['message_id'], 'message_id': 'evil_id' } url = '%s/%s/messages.json' % (self.url, self.conversation.key) response = yield http_request_full(url, json.dumps(msg), self.auth_headers, method='PUT') self.assertEqual(response.code, http.OK) put_msg = json.loads(response.delivered_body) [sent_msg] = self.app_helper.get_dispatched_outbound() # We do not respect the message_id that's been given. self.assertNotEqual(sent_msg['message_id'], msg['message_id']) self.assertEqual(sent_msg['message_id'], put_msg['message_id']) self.assertEqual(sent_msg['to_addr'], inbound_msg['from_addr']) self.assertEqual(sent_msg['from_addr'], '9292') @inlineCallbacks def test_metric_publishing(self): metric_data = [ ("vumi.test.v1", 1234, 'SUM'), ("vumi.test.v2", 3456, 'AVG'), ] url = '%s/%s/metrics.json' % (self.url, self.conversation.key) response = yield http_request_full( url, json.dumps(metric_data), self.auth_headers, method='PUT') self.assertEqual(response.code, http.OK) prefix = "go.campaigns.test-0-user.stores.metric_store" self.assertEqual( self.app_helper.get_published_metrics(self.app), [("%s.vumi.test.v1" % prefix, 1234), ("%s.vumi.test.v2" % prefix, 3456)]) @inlineCallbacks def test_concurrency_limits(self): config = yield self.app.get_config(None) concurrency = config.concurrency_limit queue = DeferredQueue() url = '%s/%s/messages.json' % (self.url, self.conversation.key) max_receivers = [self.client.stream( TransportUserMessage, queue.put, queue.put, url, Headers(self.auth_headers)) for _ in range(concurrency)] for i in range(concurrency): msg = yield self.app_helper.make_dispatch_inbound( 'in %s' % (i,), message_id=str(i), conv=self.conversation) received = yield queue.get() self.assertEqual(msg['message_id'], received['message_id']) maxed_out_resp = yield http_request_full( url, method='GET', headers=self.auth_headers) self.assertEqual(maxed_out_resp.code, 403) self.assertTrue( 'Too many concurrent connections' in maxed_out_resp.delivered_body) [r.disconnect() for r in max_receivers] @inlineCallbacks def test_disabling_concurrency_limit(self): conv_resource = StreamingConversationResource( self.app, self.conversation.key) # negative concurrency limit disables it ctxt = ConfigContext(user_account=self.conversation.user_account.key, concurrency_limit=-1) config = yield self.app.get_config(msg=None, ctxt=ctxt) self.assertTrue( (yield conv_resource.is_allowed( config, self.conversation.user_account.key))) @inlineCallbacks def test_backlog_on_connect(self): for i in range(10): yield self.app_helper.make_dispatch_inbound( 'in %s' % (i,), message_id=str(i), conv=self.conversation) queue = DeferredQueue() url = '%s/%s/messages.json' % (self.url, self.conversation.key) receiver = self.client.stream( TransportUserMessage, queue.put, queue.put, url, Headers(self.auth_headers)) for i in range(10): received = yield queue.get() self.assertEqual(received['message_id'], str(i)) receiver.disconnect() @inlineCallbacks def test_health_response(self): health_url = 'http://%s:%s%s' % ( self.addr.host, self.addr.port, self.config['health_path']) response = yield http_request_full(health_url, method='GET') self.assertEqual(response.delivered_body, '0') yield self.app_helper.make_dispatch_inbound( 'in 1', message_id='1', conv=self.conversation) queue = DeferredQueue() stream_url = '%s/%s/messages.json' % (self.url, self.conversation.key) stream_receiver = self.client.stream( TransportUserMessage, queue.put, queue.put, stream_url, Headers(self.auth_headers)) yield queue.get() response = yield http_request_full(health_url, method='GET') self.assertEqual(response.delivered_body, '1') stream_receiver.disconnect() response = yield http_request_full(health_url, method='GET') self.assertEqual(response.delivered_body, '0') self.assertEqual(self.app.client_manager.clients, { 'sphex.stream.message.%s' % (self.conversation.key,): [] }) @inlineCallbacks def test_post_inbound_message(self): # Set the URL so stuff is HTTP Posted instead of streamed. self.conversation.config['http_api'].update({ 'push_message_url': self.mock_push_server.url, }) yield self.conversation.save() msg_d = self.app_helper.make_dispatch_inbound( 'in 1', message_id='1', conv=self.conversation) req = yield self.push_calls.get() posted_json_data = req.content.read() req.finish() msg = yield msg_d posted_msg = TransportUserMessage.from_json(posted_json_data) self.assertEqual(posted_msg['message_id'], msg['message_id']) @inlineCallbacks def test_post_inbound_message_201_response(self): # Set the URL so stuff is HTTP Posted instead of streamed. self.conversation.config['http_api'].update({ 'push_message_url': self.mock_push_server.url, }) yield self.conversation.save() with LogCatcher(message='Got unexpected response code') as lc: msg_d = self.app_helper.make_dispatch_inbound( 'in 1', message_id='1', conv=self.conversation) req = yield self.push_calls.get() req.setResponseCode(201) req.finish() yield msg_d self.assertEqual(lc.messages(), []) @inlineCallbacks def test_post_inbound_message_500_response(self): # Set the URL so stuff is HTTP Posted instead of streamed. self.conversation.config['http_api'].update({ 'push_message_url': self.mock_push_server.url, }) yield self.conversation.save() with LogCatcher(message='Got unexpected response code') as lc: msg_d = self.app_helper.make_dispatch_inbound( 'in 1', message_id='1', conv=self.conversation) req = yield self.push_calls.get() req.setResponseCode(500) req.finish() yield msg_d [warning_log] = lc.messages() self.assertTrue(self.mock_push_server.url in warning_log) self.assertTrue('500' in warning_log) @inlineCallbacks def test_post_inbound_event(self): # Set the URL so stuff is HTTP Posted instead of streamed. self.conversation.config['http_api'].update({ 'push_event_url': self.mock_push_server.url, }) yield self.conversation.save() msg = yield self.app_helper.make_stored_outbound( self.conversation, 'out 1', message_id='1') event_d = self.app_helper.make_dispatch_ack( msg, conv=self.conversation) req = yield self.push_calls.get() posted_json_data = req.content.read() req.finish() ack = yield event_d self.assertEqual(TransportEvent.from_json(posted_json_data), ack) @inlineCallbacks def test_bad_urls(self): def assert_not_found(url, headers={}): d = http_request_full(self.url, method='GET', headers=headers) d.addCallback(lambda r: self.assertEqual(r.code, http.NOT_FOUND)) return d yield assert_not_found(self.url) yield assert_not_found(self.url + '/') yield assert_not_found('%s/%s' % (self.url, self.conversation.key), headers=self.auth_headers) yield assert_not_found('%s/%s/' % (self.url, self.conversation.key), headers=self.auth_headers) yield assert_not_found('%s/%s/foo' % (self.url, self.conversation.key), headers=self.auth_headers) @inlineCallbacks def test_send_message_command(self): yield self.app_helper.dispatch_command( 'send_message', user_account_key=self.conversation.user_account.key, conversation_key=self.conversation.key, command_data={ u'batch_id': u'batch-id', u'content': u'foo', u'to_addr': u'to_addr', u'msg_options': { u'helper_metadata': { u'tag': { u'tag': [u'longcode', u'default10080'] } }, u'from_addr': u'default10080', } }) [msg] = self.app_helper.get_dispatched_outbound() self.assertEqual(msg.payload['to_addr'], "to_addr") self.assertEqual(msg.payload['from_addr'], "default10080") self.assertEqual(msg.payload['content'], "foo") self.assertEqual(msg.payload['message_type'], "user_message") self.assertEqual( msg.payload['helper_metadata']['go']['user_account'], self.conversation.user_account.key) self.assertEqual( msg.payload['helper_metadata']['tag']['tag'], ['longcode', 'default10080']) @inlineCallbacks def test_process_command_send_message_in_reply_to(self): msg = yield self.app_helper.make_stored_inbound( self.conversation, "foo") yield self.app_helper.dispatch_command( 'send_message', user_account_key=self.conversation.user_account.key, conversation_key=self.conversation.key, command_data={ u'batch_id': u'batch-id', u'content': u'foo', u'to_addr': u'to_addr', u'msg_options': { u'helper_metadata': { u'tag': { u'tag': [u'longcode', u'default10080'] } }, u'transport_name': u'smpp_transport', u'in_reply_to': msg['message_id'], u'transport_type': u'sms', u'from_addr': u'default10080', } }) [sent_msg] = self.app_helper.get_dispatched_outbound() self.assertEqual(sent_msg['to_addr'], msg['from_addr']) self.assertEqual(sent_msg['content'], 'foo') self.assertEqual(sent_msg['in_reply_to'], msg['message_id'])
class TestCellulantSmsTransport(VumiTestCase): @inlineCallbacks def setUp(self): self.cellulant_sms_calls = DeferredQueue() self.mock_cellulant_sms = MockHttpServer(self.handle_request) yield self.mock_cellulant_sms.start() self.add_cleanup(self.mock_cellulant_sms.stop) self.config = { 'web_path': "foo", 'web_port': 0, 'credentials': { '2371234567': { 'username': '******', 'password': '******', }, '9292': { 'username': '******', 'password': '******', } }, 'outbound_url': self.mock_cellulant_sms.url, } self.tx_helper = self.add_helper( TransportHelper(CellulantSmsTransport)) self.transport = yield self.tx_helper.get_transport(self.config) self.transport_url = self.transport.get_transport_url() def handle_request(self, request): self.cellulant_sms_calls.put(request) return '' def mkurl(self, content, from_addr="2371234567", **kw): params = { 'SOURCEADDR': from_addr, 'DESTADDR': '12345', 'MESSAGE': content, 'ID': '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='GET') [msg] = self.tx_helper.get_dispatched_inbound() self.assertEqual(msg['transport_name'], self.tx_helper.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_outbound(self): yield self.tx_helper.make_dispatch_outbound( "hello world", to_addr="2371234567") req = yield self.cellulant_sms_calls.get() self.assertEqual(req.path, '/') self.assertEqual(req.method, 'GET') self.assertEqual({ 'username': ['other-user'], 'password': ['other-pass'], 'source': ['9292'], 'destination': ['2371234567'], 'message': ['hello world'], }, req.args) @inlineCallbacks def test_outbound_creds_selection(self): yield self.tx_helper.make_dispatch_outbound( "hello world", to_addr="2371234567", from_addr='2371234567') req = yield self.cellulant_sms_calls.get() self.assertEqual(req.path, '/') self.assertEqual(req.method, 'GET') self.assertEqual({ 'username': ['user'], 'password': ['pass'], 'source': ['2371234567'], 'destination': ['2371234567'], 'message': ['hello world'], }, req.args) yield self.tx_helper.make_dispatch_outbound( "hello world", to_addr="2371234567", from_addr='9292') req = yield self.cellulant_sms_calls.get() self.assertEqual(req.path, '/') self.assertEqual(req.method, 'GET') self.assertEqual({ 'username': ['other-user'], 'password': ['other-pass'], 'source': ['9292'], 'destination': ['2371234567'], 'message': ['hello world'], }, req.args) @inlineCallbacks def test_handle_non_ascii_input(self): url = self.mkurl(u"öæł".encode("utf-8")) response = yield http_request(url, '', method='GET') [msg] = self.tx_helper.get_dispatched_inbound() self.assertEqual(msg['transport_name'], self.tx_helper.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='GET') self.assertEqual(400, response.code) self.assertEqual(json.loads(response.delivered_body), {'unexpected_parameter': ['foo']}) @inlineCallbacks def test_missing_parameters(self): url = self.mkurl_raw(ID='12345678', DESTADDR='12345', MESSAGE='hello') response = yield http_request_full(url, '', method='GET') self.assertEqual(400, response.code) self.assertEqual(json.loads(response.delivered_body), {'missing_parameter': ['SOURCEADDR']}) @inlineCallbacks def test_ignored_parameters(self): url = self.mkurl('hello', channelID='a', keyword='b', CHANNELID='c', serviceID='d', SERVICEID='e', unsub='f') response = yield http_request(url, '', method='GET') [msg] = self.tx_helper.get_dispatched_inbound() self.assertEqual(msg['content'], "hello") self.assertEqual(json.loads(response), {'message_id': msg['message_id']})
class TestMessengerTransport(VumiTestCase): timeout = 1 @inlineCallbacks def setUp(self): self.clock = Clock() MessengerTransport.clock = self.clock self.remote_server = MockHttpServer(lambda _: 'OK') yield self.remote_server.start() self.addCleanup(self.remote_server.stop) self.tx_helper = self.add_helper( HttpRpcTransportHelper(PatchedMessengerTransport)) self.msg_helper = self.add_helper(MessageHelper()) connection_pool = HTTPConnectionPool(reactor, persistent=False) treq._utils.set_global_pool(connection_pool) @inlineCallbacks def mk_transport(self, **kw): config = { 'web_port': 0, 'web_path': '/api', 'publish_status': True, 'outbound_url': self.remote_server.url, 'username': '******', 'password': '******', } config.update(kw) transport = yield self.tx_helper.get_transport(config) transport.clock = self.clock returnValue(transport) @inlineCallbacks def test_hub_challenge(self): yield self.mk_transport() res = yield self.tx_helper.mk_request_raw(method='POST', params={ 'hub.challenge': 'foo', }) self.assertEqual(res.code, http.OK) self.assertEqual(res.delivered_body, 'foo') @inlineCallbacks def test_setup_welcome_message(self): transport = yield self.mk_transport(access_token='access-token') d = transport.setup_welcome_message( {'message': { 'text': 'This is the welcome message!' }}, 'app-id') (request_d, args, kwargs) = yield transport.request_queue.get() request_d.callback(DummyResponse(200, json.dumps({}))) method, url, data = args self.assertTrue('app-id' in url) self.assertTrue('?access_token=access-token' in url) self.assertEqual( json.loads(data)['call_to_actions'], {'message': { 'text': 'This is the welcome message!' }}) yield d @inlineCallbacks def test_inbound(self): yield self.mk_transport() res = yield self.tx_helper.mk_request_raw( method='POST', data=json.dumps({ "object": "page", "entry": [{ "id": "PAGE_ID", "time": 1457764198246, "messaging": [{ "sender": { "id": "USER_ID" }, "recipient": { "id": "PAGE_ID" }, "timestamp": 1457764197627, "message": { "mid": "mid.1457764197618:41d102a3e1ae206a38", "seq": 73, "text": "hello, world!" } }] }] })) self.assertEqual(res.code, http.OK) [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg['from_addr'], 'USER_ID') self.assertEqual(msg['to_addr'], 'PAGE_ID') self.assertEqual(msg['from_addr_type'], 'facebook_messenger') self.assertEqual(msg['content'], 'hello, world!') self.assertEqual(msg['provider'], 'facebook') self.assertEqual( msg['transport_metadata'], {'messenger': { 'mid': 'mid.1457764197618:41d102a3e1ae206a38' }}) statuses = self.tx_helper.get_dispatched_statuses() [response_status, inbound_status] = statuses self.assertEqual(response_status['status'], 'ok') self.assertEqual(response_status['component'], 'response') self.assertEqual(response_status['type'], 'response_sent') self.assertEqual(response_status['message'], 'Response sent') self.assertEqual(inbound_status['status'], 'ok') self.assertEqual(inbound_status['component'], 'inbound') self.assertEqual(inbound_status['type'], 'request_success') self.assertEqual(inbound_status['message'], 'Request successful') @inlineCallbacks def test_inbound_multiple(self): yield self.mk_transport() res = yield self.tx_helper.mk_request_raw( method='POST', data=json.dumps({ "object": "page", "entry": [{ "id": "PAGE_ID", "time": 1457764198246, "messaging": [{ "sender": { "id": "USER_ID1" }, "recipient": { "id": "PAGE_ID" }, "timestamp": 1457764197627, "message": { "mid": "mid.1457764197618:41d102a3e1ae206a38", "seq": 73, "text": "hello, world!" } }, { "sender": { "id": "USER_ID2" }, "recipient": { "id": "PAGE_ID" }, "timestamp": 1457764197627, "message": { "mid": "mid.1457764197618:41d102a3e1ae206a39", "seq": 74, "text": "hello, again!" } }] }] })) self.assertEqual(res.code, http.OK) [msg1, msg2] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg1['from_addr'], 'USER_ID1') self.assertEqual(msg1['to_addr'], 'PAGE_ID') self.assertEqual(msg1['from_addr_type'], 'facebook_messenger') self.assertEqual(msg1['content'], 'hello, world!') self.assertEqual(msg1['provider'], 'facebook') self.assertEqual( msg1['transport_metadata'], {'messenger': { 'mid': 'mid.1457764197618:41d102a3e1ae206a38' }}) self.assertEqual(msg2['from_addr'], 'USER_ID2') self.assertEqual(msg2['to_addr'], 'PAGE_ID') self.assertEqual(msg2['from_addr_type'], 'facebook_messenger') self.assertEqual(msg2['content'], 'hello, again!') self.assertEqual(msg2['provider'], 'facebook') self.assertEqual( msg2['transport_metadata'], {'messenger': { 'mid': 'mid.1457764197618:41d102a3e1ae206a39' }}) statuses = self.tx_helper.get_dispatched_statuses() [response_status, inbound_status] = statuses self.assertEqual(response_status['status'], 'ok') self.assertEqual(response_status['component'], 'response') self.assertEqual(response_status['type'], 'response_sent') self.assertEqual(response_status['message'], 'Response sent') self.assertEqual(inbound_status['status'], 'ok') self.assertEqual(inbound_status['component'], 'inbound') self.assertEqual(inbound_status['type'], 'request_success') self.assertEqual(inbound_status['message'], 'Request successful') @inlineCallbacks def test_inbound_attachments(self): yield self.mk_transport() res = yield self.tx_helper.mk_request_raw( method='POST', data=json.dumps({ "object": "page", "entry": [{ "id": "PAGE_ID", "time": 1457764198246, "messaging": [{ "sender": { "id": "USER_ID" }, "recipient": { "id": "PAGE_ID" }, "timestamp": 1457764197627, "message": { "mid": "mid.1457764197618:41d102a3e1ae206a37", "seq": 63, "attachments": [{ "type": "image", "payload": { "url": "IMAGE_URL" } }] } }] }] })) self.assertEqual(res.code, http.OK) [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg['from_addr'], 'USER_ID') self.assertEqual(msg['to_addr'], 'PAGE_ID') self.assertEqual(msg['from_addr_type'], 'facebook_messenger') self.assertEqual(msg['provider'], 'facebook') self.assertEqual(msg['content'], '') self.assertEqual( msg['transport_metadata'], { 'messenger': { 'mid': "mid.1457764197618:41d102a3e1ae206a37", "attachments": [{ "type": "image", "payload": { "url": "IMAGE_URL" } }] } }) @inlineCallbacks def test_inbound_optin(self): yield self.mk_transport() res = yield self.tx_helper.mk_request_raw( method='POST', data=json.dumps({ "object": "page", "entry": [{ "id": "PAGE_ID", "time": 1457764198246, "messaging": [{ "sender": { "id": "USER_ID" }, "recipient": { "id": "PAGE_ID" }, "timestamp": 1457764197627, "optin": { "ref": "PASS_THROUGH_PARAM" } }] }] })) self.assertEqual(res.code, http.OK) [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg['from_addr'], 'USER_ID') self.assertEqual(msg['to_addr'], 'PAGE_ID') self.assertEqual(msg['from_addr_type'], 'facebook_messenger') self.assertEqual(msg['provider'], 'facebook') self.assertEqual(msg['content'], '') self.assertEqual(msg['transport_metadata'], { 'messenger': { 'mid': None, "optin": { "ref": "PASS_THROUGH_PARAM" } } }) @inlineCallbacks def test_inbound_postback(self): yield self.mk_transport() res = yield self.tx_helper.mk_request_raw( method='POST', data=json.dumps({ "object": "page", "entry": [{ "id": "PAGE_ID", "time": 1457764198246, "messaging": [{ "sender": { "id": "USER_ID" }, "recipient": { "id": "PAGE_ID" }, "timestamp": 1457764197627, "postback": { "payload": json.dumps({ "content": "1", "in_reply_to": "12345", }) } }] }] })) self.assertEqual(res.code, http.OK) [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg['from_addr'], 'USER_ID') self.assertEqual(msg['to_addr'], 'PAGE_ID') self.assertEqual(msg['from_addr_type'], 'facebook_messenger') self.assertEqual(msg['provider'], 'facebook') self.assertEqual(msg['content'], '1') self.assertEqual(msg['in_reply_to'], '12345') self.assertEqual(msg['transport_metadata'], {'messenger': { 'mid': None }}) @inlineCallbacks def test_inbound_postback_other(self): yield self.mk_transport() res = yield self.tx_helper.mk_request_raw( method='POST', data=json.dumps({ "object": "page", "entry": [{ "id": "PAGE_ID", "time": 1457764198246, "messaging": [{ "sender": { "id": "USER_ID" }, "recipient": { "id": "PAGE_ID" }, "timestamp": 1457764197627, "postback": { "payload": json.dumps({"postback": "ocean"}) } }] }] })) self.assertEqual(res.code, http.OK) [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg['from_addr'], 'USER_ID') self.assertEqual(msg['to_addr'], 'PAGE_ID') self.assertEqual(msg['from_addr_type'], 'facebook_messenger') self.assertEqual(msg['provider'], 'facebook') self.assertEqual(msg['content'], '') self.assertEqual(msg['in_reply_to'], None) self.assertEqual(msg['transport_metadata'], {'messenger': { 'mid': None, "postback": "ocean" }}) @inlineCallbacks def test_inbound_with_user_profile(self): transport = yield self.mk_transport(access_token='the-access-token', retrieve_profile=True) d = self.tx_helper.mk_request_raw( method='POST', data=json.dumps({ "object": "page", "entry": [{ "id": "PAGE_ID", "time": 1457764198246, "messaging": [{ "sender": { "id": "USER_ID" }, "recipient": { "id": "PAGE_ID" }, "timestamp": 1457764197627, "message": { "mid": "mid.1457764197618:41d102a3e1ae206a38", "seq": 73, "text": "hello, world!" } }] }] })) (request_d, args, kwargs) = yield transport.request_queue.get() method, url, data = args # NOTE: this is the URLencoding self.assertTrue('first_name%2Clast_name%2Cprofile_pic' in url) self.assertTrue('the-access-token' in url) request_d.callback( DummyResponse( 200, json.dumps({ 'first_name': 'first-name', 'last_name': 'last-name', 'profile_pic': 'rather unpleasant', }))) res = yield d self.assertEqual(res.code, http.OK) [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual( msg['helper_metadata'], { 'messenger': { 'mid': 'mid.1457764197618:41d102a3e1ae206a38', 'first_name': 'first-name', 'last_name': 'last-name', 'profile_pic': 'rather unpleasant' } }) @inlineCallbacks def test_outbound(self): transport = yield self.mk_transport(access_token='access_token') d = self.tx_helper.make_dispatch_outbound(from_addr='456', to_addr='+123', content='hi') (request_d, args, kwargs) = yield transport.request_queue.get() method, url, data = args self.assertEqual(json.loads(data), { 'message': { 'text': 'hi', }, 'recipient': { 'id': '+123', } }) request_d.callback( DummyResponse(200, json.dumps({'message_id': 'the-message-id'}))) msg = yield d [ack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assertEqual(ack['user_message_id'], msg['message_id']) self.assertEqual(ack['sent_message_id'], 'the-message-id') [status] = self.tx_helper.get_dispatched_statuses() self.assertEqual(status['status'], 'ok') self.assertEqual(status['component'], 'outbound') self.assertEqual(status['type'], 'request_success') self.assertEqual(status['message'], 'Request successful') @inlineCallbacks def test_construct_plain_reply(self): transport = yield self.mk_transport() msg = self.msg_helper.make_outbound('hello world', to_addr='123') self.assertEqual(transport.construct_reply(msg), { 'message': { 'text': 'hello world' }, 'recipient': { 'id': '123' } }) @inlineCallbacks def test_construct_button_reply(self): transport = yield self.mk_transport() msg = self.msg_helper.make_outbound('hello world', to_addr='123', helper_metadata={ 'messenger': { 'template_type': 'button', 'text': 'hello world', 'buttons': [{ 'title': 'Jupiter', 'payload': { 'content': '1', }, }, { 'type': 'web_url', 'title': 'Mars', 'url': 'http://test', }, { 'type': 'phone_number', 'title': 'Venus', 'payload': '+271234567', }] } }) self.assertEqual( transport.construct_reply(msg), { 'recipient': { 'id': '123', }, 'message': { 'attachment': { 'type': 'template', 'payload': { 'template_type': 'button', 'text': 'hello world', 'buttons': [{ 'type': 'postback', 'title': 'Jupiter', 'payload': '{"content":"1"}', }, { 'type': 'web_url', 'title': 'Mars', 'url': 'http://test', }, { 'type': 'phone_number', 'title': 'Venus', 'payload': '+271234567', }] } } } }) @inlineCallbacks def test_construct_bad_button(self): transport = yield self.mk_transport() msg = self.msg_helper.make_outbound('hello world', to_addr='123', helper_metadata={ 'messenger': { 'template_type': 'button', 'text': 'hello world', 'buttons': [{ 'title': 'Jupiter', 'payload': { 'content': '1', }, }, { 'type': 'unknown', 'title': 'Mars', }] } }) with self.assertRaisesRegexp(UnsupportedMessage, 'Unknown button type "unknown"'): transport.construct_reply(msg) @inlineCallbacks def test_construct_quick_reply(self): transport = yield self.mk_transport() msg = self.msg_helper.make_outbound('hello world', to_addr='123', helper_metadata={ 'messenger': { 'template_type': 'quick', 'text': 'hello world', 'quick_replies': [{ 'title': 'Jupiter', 'payload': { 'content': '1', }, }, { 'type': 'text', 'title': 'Mars', 'payload': { 'content': '2', }, }, { 'type': 'text', 'title': 'Venus', 'image_url': 'http://image', 'payload': { 'content': '3', }, }, { 'type': 'location', }] } }) self.assertEqual( transport.construct_reply(msg), { 'recipient': { 'id': '123', }, 'message': { 'text': 'hello world', 'quick_replies': [ { 'content_type': 'text', 'title': 'Jupiter', 'payload': '{"content":"1"}', }, { 'content_type': 'text', 'title': 'Mars', 'payload': '{"content":"2"}', }, { 'content_type': 'text', 'title': 'Venus', 'payload': '{"content":"3"}', 'image_url': 'http://image', }, { 'content_type': 'location', }, ] } }) @inlineCallbacks def test_construct_bad_quick_reply(self): transport = yield self.mk_transport() msg = self.msg_helper.make_outbound('hello world', to_addr='123', helper_metadata={ 'messenger': { 'template_type': 'quick', 'text': 'hello world', 'quick_replies': [{ 'title': 'Jupiter', 'payload': { 'content': '1', }, }, { 'type': 'unknown', 'title': 'Mars', }] } }) with self.assertRaisesRegexp(UnsupportedMessage, 'Unknown quick reply type "unknown"'): transport.construct_reply(msg) @inlineCallbacks def test_construct_generic_reply(self): transport = yield self.mk_transport() msg = self.msg_helper.make_outbound('hello world', to_addr='123', helper_metadata={ 'messenger': { 'template_type': 'generic', 'elements': [{ 'title': 'hello world', 'subtitle': 'arf', 'item_url': 'http://test', 'buttons': [{ 'title': 'Jupiter', 'payload': { 'content': '1', }, }, { 'type': 'web_url', 'title': 'Mars', 'url': 'http://test', }, { 'type': 'element_share', }] }, { 'title': 'hello again', 'image_url': 'http://image', 'buttons': [{ 'title': 'Mercury', 'payload': { 'content': '1', }, }, { 'type': 'web_url', 'title': 'Venus', 'url': 'http://test', }] }] } }) self.maxDiff = None self.assertEqual( transport.construct_reply(msg), { 'recipient': { 'id': '123', }, 'message': { 'attachment': { 'type': 'template', 'payload': { 'template_type': 'generic', 'elements': [{ 'title': 'hello world', 'subtitle': 'arf', 'item_url': 'http://test', 'buttons': [{ 'type': 'postback', 'title': 'Jupiter', 'payload': '{"content":"1"}', }, { 'type': 'web_url', 'title': 'Mars', 'url': 'http://test', }, { 'type': 'element_share', }] }, { 'title': 'hello again', 'image_url': 'http://image', 'buttons': [{ 'type': 'postback', 'title': 'Mercury', 'payload': '{"content":"1"}', }, { 'type': 'web_url', 'title': 'Venus', 'url': 'http://test', }] }] } } } }) @inlineCallbacks def test_construct_list_reply(self): transport = yield self.mk_transport() msg = self.msg_helper.make_outbound( 'hello world', to_addr='123', helper_metadata={ 'messenger': { 'template_type': 'list', # 'top_element_style': 'compact', 'elements': [{ 'title': 'hello world', 'subtitle': 'arf', 'default_action': { 'url': 'http://test', }, 'buttons': [{ 'title': 'Jupiter', 'payload': { 'content': '1', }, }, { 'type': 'web_url', 'title': 'Mars', 'url': 'http://test', }] }, { 'title': 'hello again', 'image_url': 'http://image', 'default_action': { 'url': 'http://test', 'webview_height_ratio': 'compact', 'messenger_extensions': False, 'fallback_url': 'http://moo' }, 'buttons': [{ 'title': 'Mercury', 'payload': { 'content': '2', }, }, { 'type': 'web_url', 'title': 'Venus', 'url': 'http://test', 'webview_height_ratio': 'tall', 'messenger_extensions': True, 'fallback_url': 'http://moo' }] }], 'buttons': [{ 'title': 'Europa', 'payload': { 'content': '3', }, }] } }) self.maxDiff = None self.assertEqual( transport.construct_reply(msg), { 'recipient': { 'id': '123', }, 'message': { 'attachment': { 'type': 'template', 'payload': { 'template_type': 'list', 'top_element_style': 'compact', 'elements': [{ 'title': 'hello world', 'subtitle': 'arf', 'default_action': { 'type': 'web_url', 'url': 'http://test', }, 'buttons': [{ 'type': 'postback', 'title': 'Jupiter', 'payload': '{"content":"1"}', }, { 'type': 'web_url', 'title': 'Mars', 'url': 'http://test', }] }, { 'title': 'hello again', 'image_url': 'http://image', 'default_action': { 'type': 'web_url', 'url': 'http://test', 'webview_height_ratio': 'compact', 'messenger_extensions': False, 'fallback_url': 'http://moo' }, 'buttons': [{ 'type': 'postback', 'title': 'Mercury', 'payload': '{"content":"2"}', }, { 'type': 'web_url', 'title': 'Venus', 'url': 'http://test', 'webview_height_ratio': 'tall', 'messenger_extensions': True, 'fallback_url': 'http://moo' }] }], 'buttons': [{ 'type': 'postback', 'title': 'Europa', 'payload': '{"content":"3"}', }] } } } })
class TestPermissiveCellulantSmsTransport(VumiTestCase): @inlineCallbacks def setUp(self): self.cellulant_sms_calls = DeferredQueue() self.mock_cellulant_sms = MockHttpServer(self.handle_request) yield self.mock_cellulant_sms.start() self.add_cleanup(self.mock_cellulant_sms.stop) self.config = { 'web_path': "foo", 'web_port': 0, 'credentials': { '2371234567': { 'username': '******', 'password': '******', }, '9292': { 'username': '******', 'password': '******', } }, 'outbound_url': self.mock_cellulant_sms.url, 'validation_mode': 'permissive', } self.tx_helper = self.add_helper( TransportHelper(CellulantSmsTransport)) self.transport = yield self.tx_helper.get_transport(self.config) self.transport_url = self.transport.get_transport_url() def handle_request(self, request): self.cellulant_sms_calls.put(request) return '' def mkurl(self, content, from_addr="2371234567", **kw): params = { 'SOURCEADDR': from_addr, 'DESTADDR': '12345', 'MESSAGE': content, 'ID': '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_bad_parameter_in_permissive_mode(self): url = self.mkurl('hello', foo='bar') response = yield http_request_full(url, '', method='GET') [msg] = self.tx_helper.get_dispatched_inbound() self.assertEqual(200, response.code) self.assertEqual(json.loads(response.delivered_body), {'message_id': msg['message_id']}) @inlineCallbacks def test_missing_parameters(self): url = self.mkurl_raw(ID='12345678', DESTADDR='12345', MESSAGE='hello') response = yield http_request_full(url, '', method='GET') self.assertEqual(400, response.code) self.assertEqual(json.loads(response.delivered_body), {'missing_parameter': ['SOURCEADDR']}) @inlineCallbacks def test_ignored_parameters(self): url = self.mkurl('hello', channelID='a', keyword='b', CHANNELID='c', serviceID='d', SERVICEID='e', unsub='f') response = yield http_request(url, '', method='GET') [msg] = self.tx_helper.get_dispatched_inbound() self.assertEqual(msg['content'], "hello") self.assertEqual(json.loads(response), {'message_id': msg['message_id']})
class CmYoTransportTestCase(TransportTestCase): transport_name = 'cm' transport_type = 'sms' transport_class = CmYoTransport send_path = '/sendsms' send_port = 9999 yo_incomming_template = ('%s%s?sender=0041791234567&' 'code=%s&message=%s') @inlineCallbacks def setUp(self): DelayedCall.debug = True yield super(CmYoTransportTestCase, self).setUp() self.cmyo_sms_calls = DeferredQueue() self.mock_cmyo_sms = MockHttpServer(self.handle_request) yield self.mock_cmyo_sms.start() self.config = { 'transport_name': self.transport_name, 'login': '******', 'password': '******', 'default_origin': "+313455", 'customer_id': '3454', 'receive_path': 'yo', 'receive_port': 9998, 'outbound_url': self.mock_cmyo_sms.url, } self.transport = yield self.get_transport(self.config) self.transport_url = self.transport.get_transport_url() self.today = datetime.utcnow().date() @inlineCallbacks def tearDown(self): yield self.mock_cmyo_sms.stop() yield super(CmYoTransportTestCase, self).tearDown() def mkmsg_fail(self, user_message_id='1', failure_level='', failure_code=0, failure_reason='', transport_metadata={}): if transport_metadata is None: transport_metadata = {} return TransportEvent( event_id=RegexMatcher(r'^[0-9a-fA-F]{32}$'), event_type='delivery_report', delivery_status='failed', failure_level=failure_level, failure_code=failure_code, failure_reason=failure_reason, user_message_id=user_message_id, timestamp=UTCNearNow(), transport_name=self.transport_name, transport_metadata=transport_metadata) def mkmsg_in(self, content='Hello World', from_addr='41791234567', session_event=TransportUserMessage.SESSION_NONE, message_id='abc', transport_type=None, transport_metadata=None): if transport_type is None: transport_type = self.transport_type if transport_metadata is None: transport_metadata = {} return TransportUserMessage( from_addr='+41791234567', to_addr='9292', group=None, message_id=message_id, transport_name=self.transport_name, transport_type=transport_type, transport_metadata=transport_metadata, content=content, session_event=session_event, timestamp=UTCNearNow()) #def make_resource_worker(self, msg, code=http.OK, send_id=None): #w = get_stubbed_worker(TestResourceWorker, {}) #w.set_resources([ #(self.send_path, TestResource, (msg, code, send_id))]) #self._workers.append(w) #return w.startWorker() def handle_request(self, request): self.cmyo_sms_calls.put(request) return '' @inlineCallbacks def test_sending_sms(self): msg = self.mkmsg_out(to_addr='+41791234567', content=u'Message envoy\xe9') yield self.dispatch(msg) req = yield self.cmyo_sms_calls.get() self.assertEqual(req.path, '/') self.assertEqual(req.method, 'POST') [smsg] = self.get_dispatched_events() self.assertEqual('1', smsg['user_message_id']) self.assertEqual('1', smsg['sent_message_id']) #@inlineCallbacks #def test_sending_sms_http_failure(self): #mocked_message = "timeout" #mocked_error = http.REQUEST_TIMEOUT ##HTTP response #yield self.make_resource_worker(mocked_message, mocked_error) #yield self.dispatch(self.mkmsg_out(to_addr='256788601462')) #[smsg] = self.get_dispatched('cm.event') #self.assertEqual( #self.mkmsg_fail(failure_level='http', #failure_code=http.REQUEST_TIMEOUT, #failure_reason='timeout'), #TransportMessage.from_json(smsg.body)) #@inlineCallbacks #def test_sending_sms_service_failure(self): #mocked_message = "Error: ERROR Unknown error" ##HTTP response #yield self.make_resource_worker(mocked_message) #yield self.dispatch(self.mkmsg_out(to_addr='788601462')) #[smsg] = self.get_dispatched('cm.event') #self.assertEqual( #self.mkmsg_fail(failure_level='service', #failure_reason="Error: ERROR Unknown error"), #TransportMessage.from_json(smsg.body)) def mkurl_raw(self, **params): return self.yo_incomming_template % ( self.transport_url, self.config['receive_path'], '', 'setoffre+%23A%23BELG%3D10%2Ftete%2B300000%2Fpu%23+envoy%EF%BF%BD+depuis+SIMAgriMobile') @inlineCallbacks def test_receiving_sms(self): url = self.mkurl_raw() response = yield http_request_full(url, method='GET') [smsg] = self.get_dispatched_messages() self.assertEqual(response.code, http.OK) self.assertEqual( u'setoffre #A#BELG=10/tete+300000/pu# envoy\ufffd depuis SIMAgriMobile', smsg['content']) self.assertEqual('+313455', smsg['to_addr']) self.assertEqual('+41791234567', smsg['from_addr'])
class TestMTechKenyaTransport(VumiTestCase): transport_class = MTechKenyaTransport @inlineCallbacks def setUp(self): self.cellulant_sms_calls = DeferredQueue() self.mock_mtech_sms = MockHttpServer(self.handle_request) yield self.mock_mtech_sms.start() self.add_cleanup(self.mock_mtech_sms.stop) self.valid_creds = { 'mt_username': '******', 'mt_password': '******', } self.config = { 'web_path': "foo", 'web_port': 0, 'outbound_url': self.mock_mtech_sms.url, } self.config.update(self.valid_creds) self.tx_helper = self.add_helper( TransportHelper(self.transport_class, mobile_addr='2371234567')) self.transport = yield self.tx_helper.get_transport(self.config) self.transport_url = self.transport.get_transport_url() 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.tx_helper.get_dispatched_inbound() self.assertEqual(msg['transport_name'], self.tx_helper.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.tx_helper.get_dispatched_inbound() self.assertEqual(msg['transport_name'], self.tx_helper.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 = yield self.tx_helper.make_dispatch_outbound("hi") 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': ['hi'], }, req.args) [ack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assertEqual('ack', ack['event_type']) @inlineCallbacks def test_outbound_bad_creds(self): self.valid_creds['mt_username'] = '******' msg = yield self.tx_helper.make_dispatch_outbound("hi") 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': ['hi'], }, req.args) [nack] = yield self.tx_helper.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 = yield self.tx_helper.make_dispatch_outbound( "hi", to_addr="4471234567") 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': ['hi'], }, req.args) [nack] = yield self.tx_helper.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.tx_helper.get_dispatched_inbound() self.assertEqual(msg['transport_name'], self.tx_helper.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 = yield self.tx_helper.make_dispatch_outbound( "hi", transport_metadata={'linkID': 'link123'}) 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': ['hi'], 'linkID': ['link123'], }, req.args) [ack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assertEqual('ack', ack['event_type'])
class TestVas2NetsFailureWorker(VumiTestCase): @inlineCallbacks def setUp(self): self.persistence_helper = self.add_helper(PersistenceHelper()) self.msg_helper = self.add_helper(MessageHelper()) self.worker_helper = self.add_helper( WorkerHelper(self.msg_helper.transport_name)) self.today = datetime.utcnow().date() self.worker = yield self.mk_transport_worker({ 'transport_name': self.msg_helper.transport_name, 'url': None, 'username': '******', 'password': '******', 'owner': 'owner', 'service': 'service', 'subservice': 'subservice', 'web_receive_path': '/receive', 'web_receipt_path': '/receipt', 'web_port': 0, }) self.fail_worker = yield self.mk_failure_worker({ 'transport_name': self.msg_helper.transport_name, 'retry_routing_key': '%(transport_name)s.outbound', 'failures_routing_key': '%(transport_name)s.failures', }) def mk_transport_worker(self, config): config = self.persistence_helper.mk_config(config) return self.worker_helper.get_worker(Vas2NetsTransport, config) @inlineCallbacks def mk_failure_worker(self, config): config = self.persistence_helper.mk_config(config) worker = yield self.worker_helper.get_worker( FailureWorker, config, start=False) worker.retry_publisher = yield self.worker.publish_to("foo") yield worker.startWorker() self.redis = worker.redis returnValue(worker) @inlineCallbacks def mk_mock_server(self, body, headers=None, code=http.OK): if headers is None: headers = {'X-Nth-Smsid': 'message_id'} def handler(request): request.setResponseCode(code) for k, v in headers.items(): request.setHeader(k, v) return body self.mock_server = MockHttpServer(handler) self.add_cleanup(self.mock_server.stop) yield self.mock_server.start() self.worker.config['url'] = self.mock_server.url def get_dispatched_failures(self): return self.worker_helper.get_dispatched( None, 'failures', FailureMessage) @inlineCallbacks def get_retry_keys(self): timestamps = yield self.redis.zrange('retry_timestamps', 0, 0) retry_keys = set() for timestamp in timestamps: bucket_key = "retry_keys." + timestamp retry_keys.update((yield self.redis.smembers(bucket_key))) returnValue(retry_keys) def make_outbound(self, content, **kw): kw.setdefault('transport_metadata', {'network_id': 'network-id'}) return self.msg_helper.make_outbound(content, **kw) @inlineCallbacks def test_send_sms_success(self): yield self.mk_mock_server("Result_code: 00, Message OK") msg = self.make_outbound("outbound") yield self.worker_helper.dispatch_outbound(msg) self.assertEqual(1, len(self.worker_helper.get_dispatched_events())) self.assertEqual(0, len(self.get_dispatched_failures())) @inlineCallbacks def test_send_sms_fail(self): """ A 'No SmsId Header' error should not be retried. """ self.worker.failure_published = FailureCounter(1) yield self.mk_mock_server("Result_code: 04, Internal system error " "occurred while processing message", {}) msg = self.make_outbound("outbound") yield self.worker_helper.dispatch_outbound(msg) yield self.worker.failure_published.deferred yield self.worker_helper.kick_delivery() self.assertEqual(1, len(self.worker_helper.get_dispatched_events())) self.assertEqual(1, len(self.get_dispatched_failures())) [twisted_failure] = self.flushLoggedErrors(Vas2NetsTransportError) failure = twisted_failure.value self.assertTrue("No SmsId Header" in str(failure)) [fmsg] = self.get_dispatched_failures() self.assertTrue( "Vas2NetsTransportError: No SmsId Header" in fmsg['reason']) [nack] = self.worker_helper.get_dispatched_events() self.assertTrue( "No SmsId Header" in nack['nack_reason']) yield self.worker_helper.kick_delivery() [key] = yield self.fail_worker.get_failure_keys() self.assertEqual(set(), (yield self.get_retry_keys())) @inlineCallbacks def test_send_sms_noconn(self): """ A 'connection refused' error should be retried. """ # TODO: Figure out a solution that doesn't require hoping that # nothing's listening on this port. self.worker.config['url'] = 'http://127.0.0.1:9999/' self.worker.failure_published = FailureCounter(1) msg = self.make_outbound("outbound") yield self.worker_helper.dispatch_outbound(msg) yield self.worker.failure_published.deferred self.assertEqual(0, len(self.worker_helper.get_dispatched_events())) self.assertEqual(1, len(self.get_dispatched_failures())) [twisted_failure] = self.flushLoggedErrors(TemporaryFailure) failure = twisted_failure.value self.assertTrue("connection refused" in str(failure)) [fmsg] = self.get_dispatched_failures() self.assertEqual(msg.payload, fmsg['message']) self.assertEqual(FailureMessage.FC_TEMPORARY, fmsg['failure_code']) self.assertTrue(fmsg['reason'].strip().endswith("connection refused")) yield self.worker_helper.kick_delivery() [key] = yield self.fail_worker.get_failure_keys() self.assertEqual(set([key]), (yield self.get_retry_keys()))
class AirtelBfTransportTestCase(TransportTestCase): transport_name = "airtel" transport_type = "sms" transport_class = AirtelBfHttpTransport send_path = "/sendsms/index" send_port = 9999 @inlineCallbacks def setUp(self): yield super(AirtelBfTransportTestCase, self).setUp() self.airtel_sms_calls = DeferredQueue() self.mock_airtel_sms = MockHttpServer(self.handle_request) yield self.mock_airtel_sms.start() self.config = { "transport_name": self.transport_name, "receive_path": "sendsms", "receive_port": 9998, "outbound_url": self.mock_airtel_sms.url, "default_shortcode": "3411", "login": "******", "password": "******", } self.transport = yield self.get_transport(self.config) self.transport_url = self.transport.get_transport_url() @inlineCallbacks def tearDown(self): yield self.mock_airtel_sms.stop() yield super(AirtelBfTransportTestCase, self).tearDown() def handle_request(self, request): self.airtel_sms_calls.put(request) return "" @inlineCallbacks def test_sending_sms(self): yield self.dispatch(self.mkmsg_out()) req = yield self.airtel_sms_calls.get() self.assertEqual(req.path, "/") self.assertEqual(req.method, "GET") self.assertEqual( { "REQUESTTYPE": ["SMSSubmitReq"], "ORIGIN_ADDR": ["9292"], "MOBILENO": ["+41791234567"], "MESSAGE": ["hello world"], "USERNAME": ["texttochange"], "PASSWORD": ["password"], "TYPE": ["0"], }, req.args, ) [smsg] = self.get_dispatched_events() self.assertEqual(self.mkmsg_ack(user_message_id="1", sent_message_id="1"), smsg) @inlineCallbacks def test_sending_sms_complex(self): yield self.dispatch( self.mkmsg_out( from_addr="+2261", content=u"setoffre #A#BELG=10/tete+300000/pu# envoy\xe9 depuis SIMAgriMobile" ) ) req = yield self.airtel_sms_calls.get() self.assertEqual(req.path, "/") self.assertEqual(req.method, "GET") self.assertEqual("setoffre #A#BELG=10/tete+300000/pu# envoyé depuis SIMAgriMobile", req.args["MESSAGE"][0]) [smsg] = self.get_dispatched_events() self.assertEqual(self.mkmsg_ack(user_message_id="1", sent_message_id="1"), smsg) def mkurl_raw(self, **params): return "%s%s?%s" % (self.transport_url, self.config["receive_path"], urlencode(params)) def mkurl(self, content, from_addr, **kw): params = {"message": content, "msisdn": from_addr} params.update(kw) return self.mkurl_raw(**params) @inlineCallbacks def test_receiving_sms(self): url = self.mkurl("Hello envoy\xc3\xa9", "+2261") response = yield http_request_full(url, method="GET") [smsg] = self.get_dispatched_messages() self.assertEqual(response.code, http.OK) self.assertEqual(u"Hello envoy\xe9", smsg["content"]) self.assertEqual("3411", smsg["to_addr"]) self.assertEqual("+2261", smsg["from_addr"]) @inlineCallbacks def test_receiving_sms_accent(self): url = self.mkurl("Hello envoyé", "+2261") response = yield http_request_full(url, method="GET") [smsg] = self.get_dispatched_messages() self.assertEqual(response.code, http.OK) self.assertEqual(u"Hello envoy\xe9", smsg["content"]) self.assertEqual("3411", smsg["to_addr"]) self.assertEqual("+2261", smsg["from_addr"]) @inlineCallbacks def test_receiving_sms_fail(self): params = {"message": "Hello", "to_addr": "+2261"} url = self.mkurl_raw(**params) response = yield http_request_full(url, method="GET") self.assertEqual(0, len(self.get_dispatched_messages())) self.assertEqual(response.code, http.INTERNAL_SERVER_ERROR)
class TestMTechKenyaTransport(VumiTestCase): transport_class = MTechKenyaTransport @inlineCallbacks def setUp(self): self.cellulant_sms_calls = DeferredQueue() self.mock_mtech_sms = MockHttpServer(self.handle_request) yield self.mock_mtech_sms.start() self.add_cleanup(self.mock_mtech_sms.stop) self.valid_creds = { 'mt_username': '******', 'mt_password': '******', } self.config = { 'web_path': "foo", 'web_port': 0, 'outbound_url': self.mock_mtech_sms.url, } self.config.update(self.valid_creds) self.tx_helper = self.add_helper( TransportHelper(self.transport_class, mobile_addr='2371234567')) self.transport = yield self.tx_helper.get_transport(self.config) self.transport_url = self.transport.get_transport_url() 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.tx_helper.get_dispatched_inbound() self.assertEqual(msg['transport_name'], self.tx_helper.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.tx_helper.get_dispatched_inbound() self.assertEqual(msg['transport_name'], self.tx_helper.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 = yield self.tx_helper.make_dispatch_outbound("hi") 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': ['hi'], }, req.args) [ack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assertEqual('ack', ack['event_type']) @inlineCallbacks def test_outbound_bad_creds(self): self.valid_creds['mt_username'] = '******' msg = yield self.tx_helper.make_dispatch_outbound("hi") 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': ['hi'], }, req.args) [nack] = yield self.tx_helper.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 = yield self.tx_helper.make_dispatch_outbound("hi", to_addr="4471234567") 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': ['hi'], }, req.args) [nack] = yield self.tx_helper.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.tx_helper.get_dispatched_inbound() self.assertEqual(msg['transport_name'], self.tx_helper.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 = yield self.tx_helper.make_dispatch_outbound( "hi", transport_metadata={'linkID': 'link123'}) 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': ['hi'], 'linkID': ['link123'], }, req.args) [ack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assertEqual('ack', ack['event_type'])
class TestIntegratTransport(VumiTestCase): @inlineCallbacks def setUp(self): self.integrat_calls = DeferredQueue() self.mock_integrat = MockHttpServer(self.handle_request) self.add_cleanup(self.mock_integrat.stop) yield self.mock_integrat.start() config = { 'web_path': "foo", 'web_port': "0", 'url': self.mock_integrat.url, 'username': '******', 'password': '******', } self.tx_helper = self.add_helper(TransportHelper(IntegratTransport)) self.transport = yield self.tx_helper.get_transport(config) addr = self.transport.web_resource.getHost() self.transport_url = "http://%s:%s/" % (addr.host, addr.port) self.higate_response = '<Response status_code="0"/>' def handle_request(self, request): # The content attr will have been set to None by the time we read this. request.content_body = request.content.getvalue() self.integrat_calls.put(request) return self.higate_response @inlineCallbacks def test_health(self): result = yield http_request(self.transport_url + "health", "", method='GET') self.assertEqual(result, "OK") @inlineCallbacks def test_outbound(self): yield self.tx_helper.make_dispatch_outbound("hi", transport_metadata={ 'session_id': "sess123", }) req = yield self.integrat_calls.get() self.assertEqual(req.path, '/') self.assertEqual(req.method, 'POST') self.assertEqual(req.getHeader('content-type'), 'text/xml; charset=utf-8') self.assertEqual(req.content_body, '<Message><Version Version="1.0" />' '<Request Flags="0" SessionID="sess123"' ' Type="USSReply">' '<UserID Orientation="TR">testuser</UserID>' '<Password>testpass</Password>' '<USSText Type="TEXT">hi</USSText>' '</Request></Message>') @inlineCallbacks def test_outbound_no_content(self): yield self.tx_helper.make_dispatch_outbound(None, transport_metadata={ 'session_id': "sess123", }) req = yield self.integrat_calls.get() self.assertEqual(req.path, '/') self.assertEqual(req.method, 'POST') self.assertEqual(req.getHeader('content-type'), 'text/xml; charset=utf-8') self.assertEqual(req.content_body, '<Message><Version Version="1.0" />' '<Request Flags="0" SessionID="sess123"' ' Type="USSReply">' '<UserID Orientation="TR">testuser</UserID>' '<Password>testpass</Password>' '<USSText Type="TEXT" />' '</Request></Message>') @inlineCallbacks def test_inbound(self): xml = XML_TEMPLATE % { 'ussd_type': 'Request', 'sid': 'sess1234', 'network_sid': "netsid12345", 'msisdn': '27345', 'connstr': '*120*99#', 'text': 'foobar', } yield http_request(self.transport_url + "foo", xml, method='GET') [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg['transport_name'], self.tx_helper.transport_name) self.assertEqual(msg['transport_type'], "ussd") self.assertEqual(msg['transport_metadata'], {"session_id": "sess1234"}) self.assertEqual(msg['session_event'], TransportUserMessage.SESSION_RESUME) self.assertEqual(msg['from_addr'], '27345') self.assertEqual(msg['to_addr'], '*120*99#') self.assertEqual(msg['content'], 'foobar') @inlineCallbacks def test_inbound_non_ascii(self): xml = (XML_TEMPLATE % { 'ussd_type': 'Request', 'sid': 'sess1234', 'network_sid': "netsid12345", 'msisdn': '27345', 'connstr': '*120*99#', 'text': u'öæł', }).encode("utf-8") yield http_request(self.transport_url + "foo", xml, method='GET') [msg] = yield self.tx_helper.wait_for_dispatched_inbound(1) self.assertEqual(msg['content'], u'öæł') @inlineCallbacks def test_nack(self): self.higate_response = """ <Response status_code="-1"> <Data name="method_error"> <field name="error_code" value="-1"/> <field name="reason" value="Expecting POST, not GET"/> </Data> </Response>""".strip() msg = yield self.tx_helper.make_dispatch_outbound( "hi", transport_metadata={'session_id': "sess123"}) yield self.integrat_calls.get() [nack] = yield self.tx_helper.wait_for_dispatched_events(1) self.assertEqual(nack['user_message_id'], msg['message_id']) self.assertEqual(nack['sent_message_id'], msg['message_id']) self.assertEqual(nack['nack_reason'], 'error_code: -1, reason: Expecting POST, not GET')
class TestNoStreamingHTTPWorkerBase(VumiTestCase): @inlineCallbacks def setUp(self): self.app_helper = self.add_helper( AppWorkerHelper(NoStreamingHTTPWorker)) self.config = { 'health_path': '/health/', 'web_path': '/foo', 'web_port': 0, 'metrics_prefix': 'metrics_prefix.', } self.app = yield self.app_helper.get_app_worker(self.config) self.addr = self.app.webserver.getHost() self.url = 'http://%s:%s%s' % (self.addr.host, self.addr.port, self.config['web_path']) # Mock server to test HTTP posting of inbound messages & events self.mock_push_server = MockHttpServer(self.handle_request) yield self.mock_push_server.start() self.add_cleanup(self.mock_push_server.stop) self.push_calls = DeferredQueue() self.conversation = yield self.create_conversation( self.get_message_url(), self.get_event_url(), ['token-1', 'token-2', 'token-3']) self.auth_headers = { 'Authorization': [ 'Basic ' + base64.b64encode( '%s:%s' % (self.conversation.user_account.key, 'token-1')) ], } self._setup_wait_for_request() self.add_cleanup(self._wait_for_requests) def get_message_url(self): return self.mock_push_server.url def get_event_url(self): return self.mock_push_server.url @inlineCallbacks def create_conversation(self, message_url, event_url, tokens): config = { 'http_api_nostream': { 'api_tokens': tokens, 'push_message_url': message_url, 'push_event_url': event_url, 'metric_store': 'metric_store', } } conv = yield self.app_helper.create_conversation(config=config) yield self.app_helper.start_conversation(conv) conversation = yield self.app_helper.get_conversation(conv.key) returnValue(conversation) def _setup_wait_for_request(self): # Hackery to wait for the request to finish self._req_state = { 'queue': DeferredQueue(), 'expected': 0, } orig_track = ConversationResource.track_request orig_release = ConversationResource.release_request def track_wrapper(*args, **kw): self._req_state['expected'] += 1 return orig_track(*args, **kw) def release_wrapper(*args, **kw): return orig_release(*args, **kw).addCallback(self._req_state['queue'].put) self.patch(ConversationResource, 'track_request', track_wrapper) self.patch(ConversationResource, 'release_request', release_wrapper) @inlineCallbacks def _wait_for_requests(self): while self._req_state['expected'] > 0: yield self._req_state['queue'].get() self._req_state['expected'] -= 1 def handle_request(self, request): self.push_calls.put(request) return NOT_DONE_YET def assert_bad_request(self, response, reason): self.assertEqual(response.code, http.BAD_REQUEST) data = json.loads(response.delivered_body) self.assertEqual(data, { "success": False, "reason": reason, })