예제 #1
0
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)
예제 #2
0
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(), )})
예제 #3
0
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(),)
            })
예제 #4
0
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')
예제 #5
0
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:
예제 #6
0
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:
예제 #7
0
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)
예제 #8
0
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")
예제 #9
0
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"}',
                            }]
                        }
                    }
                }
            })
예제 #11
0
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,
        })
예제 #12
0
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']})
예제 #13
0
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')
예제 #14
0
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'])
예제 #15
0
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)
예제 #16
0
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)
예제 #18
0
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',
        })
예제 #19
0
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'öæł')
예제 #20
0
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)
예제 #21
0
파일: test_mxit.py 프로젝트: Nagato23/vumi
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 = "&lt;&amp;&gt;"
        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))
예제 #22
0
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']})
예제 #23
0
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)
예제 #24
0
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')
예제 #25
0
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')
예제 #26
0
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'öæł')
예제 #27
0
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'))
예제 #28
0
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'])
예제 #29
0
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']})
예제 #30
0
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"}',
                            }]
                        }
                    }
                }
            })
예제 #31
0
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'])
예제 #33
0
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'])
예제 #34
0
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)
예제 #36
0
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'])
예제 #37
0
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')
예제 #38
0
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,
        })