Example #1
0
    def test_parse_query(self, mock_post):
        mock_post.return_value = MockResponse(200, '{"query":"name ~ \\"frank\\"","fields":["name"]}')
        response = get_client().parse_query(self.org.id, "frank")

        self.assertEqual('name ~ "frank"', response["query"])
        mock_post.assert_called_once_with(
            "http://localhost:8090/mr/contact/parse_query",
            headers={"User-Agent": "Temba"},
            json={"query": "frank", "org_id": self.org.id, "group_uuid": ""},
        )

        mock_post.return_value = MockResponse(400, '{"error":"no such field age"}')

        with self.assertRaises(MailroomException):
            get_client().parse_query(1, "age > 10")
Example #2
0
    def test_connect(self):
        url = reverse("classifiers.classifier_connect")
        response = self.client.get(url)
        self.assertRedirect(response, "/users/login/")

        self.login(self.admin)
        response = self.client.get(url)

        # should have url for claiming our type
        url = reverse("classifiers.types.luis.connect")
        self.assertContains(response, url)

        response = self.client.get(url)
        post_data = response.context["form"].initial

        # will fail as we don't have anything filled out
        response = self.client.post(url, post_data)
        self.assertFormError(response, "form", "app_id", ["This field is required."])

        post_data["name"] = "Booker"
        post_data["app_id"] = "12345"
        post_data["version"] = "0.1"
        post_data["primary_key"] = "sesame"
        post_data["endpoint_url"] = "http://nyaruka.com/luis"

        # can't connect
        with patch("requests.get") as mock_get:
            mock_get.return_value = MockResponse(400, '{ "error": "true" }')
            response = self.client.post(url, post_data)
            self.assertEqual(200, response.status_code)
            self.assertFalse(Classifier.objects.all())

            self.assertContains(response, "Unable to get intents for your app")

        # all good
        with patch("requests.get") as mock_get:
            mock_get.side_effect = [MockResponse(200, '{ "error": "false" }'), MockResponse(200, INTENT_RESPONSE)]
            response = self.client.post(url, post_data)
            self.assertEqual(302, response.status_code)

            c = Classifier.objects.get()
            self.assertEqual("Booker", c.name)
            self.assertEqual("luis", c.classifier_type)
            self.assertEqual("sesame", c.config[LuisType.CONFIG_PRIMARY_KEY])
            self.assertEqual("http://nyaruka.com/luis", c.config[LuisType.CONFIG_ENDPOINT_URL])
            self.assertEqual("0.1", c.config[LuisType.CONFIG_VERSION])

            self.assertEqual(2, c.intents.all().count())
Example #3
0
    def test_claim(self, mock_get):
        url = reverse('channels.claim_firebase')

        self.login(self.admin)

        # check that claim page URL appears on claim list page
        response = self.client.get(reverse('channels.channel_claim'))
        self.assertContains(response, url)

        mock_get.return_value = MockResponse(
            200,
            json.dumps({
                'title': 'FCM Channel',
                'key': 'abcde12345',
                'send_notification': 'True'
            }))

        response = self.client.post(url, {
            'title': 'FCM Channel',
            'key': 'abcde12345',
            'send_notification': 'True'
        },
                                    follow=True)

        channel = Channel.objects.get(address='abcde12345')
        self.assertRedirects(
            response,
            reverse('channels.channel_configuration', args=[channel.id]))
        self.assertEqual(channel.channel_type, "FCM")
        self.assertEqual(
            channel.config_json(), {
                'FCM_KEY': 'abcde12345',
                'FCM_TITLE': 'FCM Channel',
                'FCM_NOTIFICATION': True
            })
Example #4
0
    def test_syncing(self):
        # will fail due to missing keys
        self.c1.async_sync()

        # no intents should have been changed / removed as this was an error
        self.assertEqual(2, self.c1.active_intents().count())

        # ok, fix our config
        self.c1.config = {WitType.CONFIG_ACCESS_TOKEN: "sesasme", WitType.CONFIG_APP_ID: "1234"}
        self.c1.save()

        # try again
        with patch("requests.get") as mock_get:
            mock_get.return_value = MockResponse(200, INTENT_RESPONSE)
            self.c1.async_sync()

            # should have three active intents
            intents = self.c1.active_intents()
            self.assertEqual(3, intents.count())
            self.assertEqual("book_car", intents[0].name)
            self.assertEqual("754569408690533", intents[0].external_id)
            self.assertEqual("book_horse", intents[1].name)
            self.assertEqual("754569408690020", intents[1].external_id)
            self.assertEqual("book_hotel", intents[2].name)
            self.assertEqual("754569408690131", intents[2].external_id)

            # one inactive
            self.assertEqual(1, self.c1.intents.filter(is_active=False).count())

            # one classifier log
            self.assertEqual(1, HTTPLog.objects.filter(classifier=self.c1, org=self.org).count())
Example #5
0
    def test_claim(self, mock_get):
        url = reverse("channels.types.firebase.claim")

        self.login(self.admin)

        # check that claim page URL appears on claim list page
        response = self.client.get(reverse("channels.channel_claim"))
        self.assertContains(response, url)

        mock_get.return_value = MockResponse(
            200, json.dumps({"title": "FCM Channel", "key": "abcde12345", "send_notification": "True"})
        )
        response = self.client.post(
            url, {"title": "FCM Channel", "key": "abcde12345", "send_notification": "True"}, follow=True
        )

        channel = Channel.objects.get(address="abcde12345")
        self.assertRedirects(response, reverse("channels.channel_configuration", args=[channel.uuid]))
        self.assertEqual(channel.channel_type, "FCM")
        self.assertEqual(
            channel.config, {"FCM_KEY": "abcde12345", "FCM_TITLE": "FCM Channel", "FCM_NOTIFICATION": True}
        )

        response = self.client.get(reverse("channels.channel_configuration", args=[channel.uuid]))
        self.assertContains(response, reverse("courier.fcm", args=[channel.uuid, "receive"]))
        self.assertContains(response, reverse("courier.fcm", args=[channel.uuid, "register"]))
Example #6
0
    def test_release(self, mock_delete):
        mock_delete.return_value = MockResponse(200, json.dumps({"success": True}))
        self.channel.release()

        mock_delete.assert_called_once_with(
            "https://graph.facebook.com/v2.12/me/subscribed_apps", params={"access_token": "09876543"}
        )
Example #7
0
    def test_validation_failure(self):
        with patch("requests.post") as mock_post:
            mock_post.return_value = MockResponse(
                422, '{"error":"flow don\'t look right"}')

            with self.assertRaises(FlowValidationException) as e:
                get_client().flow_inspect({"nodes": []},
                                          validate_with_org=self.org)

        self.assertEqual(str(e.exception), "flow don't look right")
        self.assertEqual(
            e.exception.as_json(),
            {
                "endpoint": "flow/inspect",
                "request": {
                    "flow": {
                        "nodes": []
                    },
                    "validate_with_org_id": self.org.id
                },
                "response": {
                    "error": "flow don't look right"
                },
            },
        )
Example #8
0
    def test_get_app(self, mock_get):
        client = AuthoringClient("http://nyaruka-authoring.luis.api", "235252111")

        mock_get.return_value = MockResponse(400, '{"error": "yes"}')

        with self.assertRaises(RequestException):
            client.get_app("123456")

        self.assertEqual(1, len(client.logs))
        self.assertEqual("http://nyaruka-authoring.luis.apiluis/api/v2.0/apps/123456", client.logs[0]["url"])

        mock_get.return_value = MockResponse(200, GET_APP_RESPONSE)
        app_info = client.get_app("123456")

        self.assertEqual(json.loads(GET_APP_RESPONSE), app_info)
        self.assertEqual(2, len(client.logs))
Example #9
0
    def test_ping(self, mock_post, mock_request_key):
        mock_request_key.return_value = "123456"
        mock_post.return_value = MockResponse(
            200,
            "info_txt=pong\r\n"
            "authentication_key=123456\r\n"
            "error_code=0\r\n"
            "error_txt=Transaction successful\r\n",
        )

        response = self.client.ping()

        self.assertEqual(
            {
                "authentication_key": "123456",
                "error_code": "0",
                "error_txt": "Transaction successful",
                "info_txt": "pong",
            },
            response,
        )
        mock_post.assert_called_once_with(
            "https://airtime-api.dtone.com/cgi-bin/shop/topup",
            {"login": "******", "key": "123456", "md5": "4ff2ddfae96f8d902eb7d5b2c7b490c9", "action": "ping"},
        )
Example #10
0
    def test_check_wallet(self, mock_post, mock_request_key):
        mock_request_key.return_value = "123456"
        mock_post.return_value = MockResponse(
            200,
            "type=Master\r\n"
            "authentication_key=123456\r\n"
            "error_code=0\r\n"
            "error_txt=Transaction successful\r\n"
            "balance=15000\r\n"
            "currency=RWF\r\n",
        )

        response = self.client.check_wallet()

        self.assertEqual(
            {
                "type": "Master",
                "authentication_key": "123456",
                "error_code": "0",
                "error_txt": "Transaction successful",
                "balance": "15000",
                "currency": "RWF",
            },
            response,
        )
        mock_post.assert_called_once_with(
            "https://airtime-api.dtone.com/cgi-bin/shop/topup",
            {"login": "******", "key": "123456", "md5": "4ff2ddfae96f8d902eb7d5b2c7b490c9", "action": "check_wallet"},
        )
Example #11
0
    def test_flow_event(self):
        self.setupChannel()

        org = self.channel.org
        org.save()

        from temba.flows.models import ActionSet, WebhookAction, Flow
        flow = self.create_flow()

        # replace our uuid of 4 with the right thing
        actionset = ActionSet.objects.get(x=4)
        actionset.set_actions_dict(
            [WebhookAction(org.get_webhook_url()).as_json()])
        actionset.save()

        with patch('requests.Session.send') as mock:
            # run a user through this flow
            flow.start([], [self.joe])

            # have joe reply with mauve, which will put him in the other category that triggers the API Action
            sms = self.create_msg(contact=self.joe,
                                  direction='I',
                                  status='H',
                                  text="Mauve")

            mock.return_value = MockResponse(200, "{}")
            Flow.find_and_handle(sms)

            # should have one event created
            event = WebHookEvent.objects.get()

            self.assertEquals('C', event.status)
            self.assertEquals(1, event.try_count)
            self.assertFalse(event.next_attempt)

            result = WebHookResult.objects.get()
            self.assertStringContains("successfully", result.message)
            self.assertEquals(200, result.status_code)

            self.assertTrue(mock.called)

            args = mock.call_args_list[0][0]
            prepared_request = args[0]
            self.assertStringContains(self.channel.org.get_webhook_url(),
                                      prepared_request.url)

            data = parse_qs(prepared_request.body)
            self.assertEquals(self.channel.pk, int(data['channel'][0]))
            self.assertEquals(actionset.uuid, data['step'][0])
            self.assertEquals(flow.pk, int(data['flow'][0]))
            self.assertEquals(self.joe.uuid, data['contact'][0])
            self.assertEquals(unicode(self.joe.get_urn('tel')), data['urn'][0])

            values = json.loads(data['values'][0])

            self.assertEquals('Other', values[0]['category']['base'])
            self.assertEquals('color', values[0]['label'])
            self.assertEquals('Mauve', values[0]['text'])
            self.assertTrue(values[0]['time'])
            self.assertTrue(data['time'])
Example #12
0
    def test_predict(self, mock_get):
        client = PredictionClient("http://nyaruka.luis.api", "235252111")

        mock_get.return_value = MockResponse(400, '{"error": "yes"}')

        with self.assertRaises(RequestException):
            client.predict("123456", "production", "hello")

        mock_get.return_value = MockResponse(200, """{"query": "Hello"}""")
        mock_get.reset_mock()

        client.predict("123456", "production", "hello")

        mock_get.assert_called_once_with(
            "http://nyaruka.luis.apiluis/prediction/v3.0/apps/123456/slots/production/predict?query=hello&subscription-key=235252111"
        )
Example #13
0
    def test_webhook_first(self, mock_send):
        mock_send.return_value = MockResponse(200, "{}")

        self.setupChannel()
        org = self.channel.org
        org.save()

        # set our very first action to be a webhook
        flow = self.get_flow("webhook_rule_first")

        # run a user through this flow
        flow.start([], [self.joe])
        event = WebHookEvent.objects.get()

        # make sure our contact still has a URN
        self.assertEqual(
            event.data["contact"],
            {
                "uuid": str(self.joe.uuid),
                "name": self.joe.name,
                "urn": str(self.joe.get_urn("tel"))
            },
        )

        # make sure we don't have an input
        self.assertNotIn("input", event.data)
Example #14
0
    def test_contact_resolve(self, mock_post):
        mock_post.return_value = MockResponse(
            200, '{"contact": {"id": 1234}, "urn": {"id": 2345}}')

        # try with empty contact spec
        response = get_client().contact_resolve(self.org.id, 345,
                                                "tel:+1234567890")

        self.assertEqual({
            "contact": {
                "id": 1234
            },
            "urn": {
                "id": 2345
            }
        }, response)
        mock_post.assert_called_once_with(
            "http://localhost:8090/mr/contact/resolve",
            headers={"User-Agent": "Temba"},
            json={
                "org_id": self.org.id,
                "channel_id": 345,
                "urn": "tel:+1234567890"
            },
        )
Example #15
0
    def test_new_conversation_triggers(self, mock_post):
        mock_post.return_value = MockResponse(200, json.dumps({'success': True}))

        flow = self.create_flow()

        trigger = Trigger.create(self.org, self.admin, Trigger.TYPE_NEW_CONVERSATION, flow, self.channel)

        mock_post.assert_called_once_with('https://graph.facebook.com/v2.6/12345/thread_settings', json={
            'setting_type': 'call_to_actions',
            'thread_state': 'new_thread',
            'call_to_actions': [{"payload": "get_started"}]
        }, headers={'Content-Type': 'application/json'}, params={'access_token': '09876543'})
        mock_post.reset_mock()

        trigger.archive(self.admin)

        mock_post.assert_called_once_with('https://graph.facebook.com/v2.6/12345/thread_settings', json={
            'setting_type': 'call_to_actions',
            'thread_state': 'new_thread',
            'call_to_actions': []
        }, headers={'Content-Type': 'application/json'}, params={'access_token': '09876543'})
        mock_post.reset_mock()

        trigger.restore(self.admin)

        mock_post.assert_called_once_with('https://graph.facebook.com/v2.6/12345/thread_settings', json={
            'setting_type': 'call_to_actions',
            'thread_state': 'new_thread',
            'call_to_actions': [{"payload": "get_started"}]
        }, headers={'Content-Type': 'application/json'}, params={'access_token': '09876543'})
        mock_post.reset_mock()
Example #16
0
    def test_webhook_result_timing(self, mock_time):
        mock_time.side_effect = [1, 1, 1, 6, 6]

        sms = self.create_msg(contact=self.joe,
                              direction='I',
                              status='H',
                              text="I'm gonna pop some tags")
        self.setupChannel()
        now = timezone.now()

        with patch('requests.Session.send') as mock:
            mock.return_value = MockResponse(200, "Hello World")

            # trigger an event
            WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms,
                                           now)
            event = WebHookEvent.objects.get()

            self.assertEquals('C', event.status)
            self.assertEquals(1, event.try_count)
            self.assertFalse(event.next_attempt)

            result = WebHookResult.objects.get()
            self.assertIn("Event delivered successfully", result.message)
            self.assertIn("not JSON", result.message)
            self.assertEquals(200, result.status_code)
            self.assertEqual(result.request_time, 5000)

            self.assertTrue(mock_time.called)
            self.assertTrue(mock.called)
Example #17
0
    def test_send_default_url(self):
        joe = self.create_contact("Joe", "+250788383383")
        self.create_group("Reporters", [joe])
        inbound = Msg.create_incoming(self.channel,
                                      "tel:+250788383383",
                                      "Send an inbound message",
                                      external_id='vumi-message-id')
        msg = inbound.reply("Test message", self.admin, trigger_send=False)

        # our outgoing message
        msg.refresh_from_db()

        try:
            settings.SEND_MESSAGES = True

            with patch('requests.put') as mock:
                mock.return_value = MockResponse(200,
                                                 '{ "message_id": "1515" }')

                # manually send it off
                Channel.send_message(
                    dict_to_struct('MsgStruct', msg.as_task_json()))

                self.assertEqual(
                    mock.call_args[0][0],
                    'https://go.vumi.org/api/v1/go/http_api_nostream/key/messages.json'
                )

                self.clear_cache()

        finally:
            settings.SEND_MESSAGES = False
Example #18
0
    def test_channel_log(self):
        channel = self.create_channel("WA", "WhatsApp: 1234", "1234")

        exception = RequestException("Network is unreachable",
                                     response=MockResponse(100, ""))
        start = timezone.now()

        log1 = HTTPLog.create_from_exception(
            HTTPLog.WHATSAPP_TEMPLATES_SYNCED,
            "https://graph.facebook.com/v3.3/1234/message_templates",
            exception,
            start,
            channel=channel,
        )

        self.login(self.admin)
        log_url = reverse("request_logs.httplog_read", args=[log1.id])
        response = self.client.get(log_url)
        self.assertContains(response, "200")
        self.assertContains(response, "Connection Error")
        self.assertContains(
            response, "https://graph.facebook.com/v3.3/1234/message_templates")

        # and can't be from other org
        self.login(self.admin2)
        response = self.client.get(log_url)
        self.assertLoginRedirect(response)
Example #19
0
    def test_non_blocking_rule_ivr(self):

        self.org.connect_twilio("TEST_SID", "TEST_TOKEN", self.admin)
        self.org.save()

        # flow goes: passive -> recording -> msg
        flow = self.get_flow("non_blocking_rule_ivr")

        print json.dumps(flow.as_json(), indent=2)

        # start marshall in the flow
        eminem = self.create_contact("Eminem", "+12345")
        flow.start(groups=[], contacts=[eminem])
        call = IVRCall.objects.filter(direction=OUTGOING).first()
        self.assertNotEquals(call, None)

        # after a call is picked up, twilio will call back to our server
        post_data = dict(CallSid="CallSid", CallStatus="in-progress", CallDuration=20)
        self.client.post(reverse("ivr.ivrcall_handle", args=[call.pk]), post_data)

        # should have two steps so far, right up to the recording
        self.assertEquals(2, FlowStep.objects.all().count())

        # no outbound yet
        self.assertEquals(None, Msg.all_messages.filter(direction="O", contact=eminem).first())

        # now pretend we got a recording
        from temba.tests import MockResponse

        with patch("requests.get") as response:
            mock = MockResponse(200, "Fake Recording Bits")
            mock.add_header("Content-Disposition", 'filename="audio0000.wav"')
            mock.add_header("Content-Type", "audio/x-wav")
            response.return_value = mock

            self.client.post(
                reverse("ivr.ivrcall_handle", args=[call.pk]),
                dict(
                    CallStatus="in-progress",
                    Digits="#",
                    RecordingUrl="http://api.twilio.com/ASID/Recordings/SID",
                    RecordingSid="FAKESID",
                ),
            )

        # now we should have an outbound message
        self.assertEquals("Hi there Eminem", Msg.all_messages.filter(direction="O", contact=eminem).first().text)
Example #20
0
    def test_get_api_templates(self, mock_get):
        TemplateTranslation.objects.all().delete()
        Channel.objects.all().delete()
        channel = self.create_channel(
            "D3",
            "360Dialog channel",
            address="1234",
            country="BR",
            config={
                Channel.CONFIG_BASE_URL: "https://example.com/whatsapp",
                Channel.CONFIG_AUTH_TOKEN: "123456789",
            },
        )

        mock_get.side_effect = [
            RequestException("Network is unreachable",
                             response=MockResponse(100, "")),
            MockResponse(400, '{ "meta": { "success": false } }'),
            MockResponse(200, '{"waba_templates": ["foo", "bar"]}'),
        ]

        # RequestException check HTTPLog
        templates_data, no_error = Dialog360Type().get_api_templates(channel)
        self.assertEqual(
            1,
            HTTPLog.objects.filter(
                log_type=HTTPLog.WHATSAPP_TEMPLATES_SYNCED).count())
        self.assertFalse(no_error)
        self.assertEqual([], templates_data)

        # should be empty list with an error flag if fail with API
        templates_data, no_error = Dialog360Type().get_api_templates(channel)
        self.assertFalse(no_error)
        self.assertEqual([], templates_data)

        # success no error and list
        templates_data, no_error = Dialog360Type().get_api_templates(channel)
        self.assertTrue(no_error)
        self.assertEqual(["foo", "bar"], templates_data)

        mock_get.assert_called_with(
            "https://example.com/whatsapp/v1/configs/templates",
            headers={
                "D360-API-KEY": channel.config[Channel.CONFIG_AUTH_TOKEN],
                "Content-Type": "application/json",
            },
        )
Example #21
0
    def test_claim_self_hosted_templates(self):
        Channel.objects.all().delete()

        url = reverse("channels.types.whatsapp.claim")
        self.login(self.admin)

        response = self.client.get(reverse("channels.channel_claim"))
        self.assertNotContains(response, url)

        response = self.client.get(url)
        self.assertEqual(200, response.status_code)
        post_data = response.context["form"].initial

        post_data["number"] = "0788123123"
        post_data["username"] = "******"
        post_data["password"] = "******"
        post_data["country"] = "RW"
        post_data["base_url"] = "https://whatsapp.foo.bar"
        post_data["facebook_namespace"] = "my-custom-app"
        post_data["facebook_business_id"] = "1234"
        post_data["facebook_access_token"] = "token123"
        post_data["facebook_template_list_domain"] = "example.org"

        # success claim
        with patch("requests.post") as mock_post:
            with patch("requests.get") as mock_get:

                mock_post.return_value = MockResponse(200, '{"users": [{"token": "abc123"}]}')
                mock_get.return_value = MockResponse(200, '{"data": []}')

                response = self.client.post(url, post_data)
                self.assertEqual(302, response.status_code)
                mock_get.assert_called_with(
                    "https://example.org/v3.3/1234/message_templates", params={"access_token": "token123"}
                )

        # test the template syncing task calls the correct domain
        with patch("requests.get") as mock_get:
            mock_get.return_value = MockResponse(200, '{"data": []}')
            refresh_whatsapp_templates()
            mock_get.assert_called_with(
                "https://example.org/v3.3/1234/message_templates", params={"access_token": "token123", "limit": 255}
            )

        channel = Channel.objects.get()

        self.assertEqual("example.org", channel.config[CONFIG_FB_TEMPLATE_LIST_DOMAIN])
Example #22
0
    def test_release(self, mock_delete):
        mock_delete.return_value = MockResponse(200,
                                                json.dumps({'success': True}))
        self.channel.release()

        mock_delete.assert_called_once_with(
            'https://graph.facebook.com/v2.5/me/subscribed_apps',
            params={'access_token': '09876543'})
Example #23
0
 def test_settings_success(self, mock_request):
     mock_request.return_value = MockResponse(204, {})
     try:
         Client(self.secure_url,
                self.secret).settings("http://temba.io/c/1234-5678",
                                      "test-bot")
     except ClientError:
         self.fail("The status 204 should not raise exceptions")
Example #24
0
 def test_settings_fail(self):
     for status in range(200, 599):
         if status == 204:
             continue
         with patch("requests.put") as mock_request:
             mock_request.return_value = MockResponse(status, {})
             with self.assertRaises(ClientError, msg=f"The status {status} must be invalid"):
                 Client(self.secure_url, self.secret).settings("http://temba.io/mr/tickets/1234-5678")
Example #25
0
    def test_ack(self):
        joe = self.create_contact("Joe", "+250788383383")
        self.create_group("Reporters", [joe])
        inbound = Msg.create_incoming(self.channel,
                                      "tel:+250788383383",
                                      "Send an inbound message",
                                      external_id='vumi-message-id',
                                      msg_type=USSD)
        msg = inbound.reply("Test message", self.admin, trigger_send=False)[0]

        # our outgoing message
        msg.refresh_from_db()

        try:
            settings.SEND_MESSAGES = True

            with patch('requests.put') as mock:
                mock.return_value = MockResponse(200,
                                                 '{ "message_id": "1515" }')

                # manually send it off
                Channel.send_message(
                    dict_to_struct('MsgStruct', msg.as_task_json()))

                # check the status of the message is now sent
                msg.refresh_from_db()
                self.assertEquals(WIRED, msg.status)
                self.assertTrue(msg.sent_on)
                self.assertEquals("1515", msg.external_id)
                self.assertEquals(1, mock.call_count)

                # simulate Vumi calling back to us sending an ACK event
                data = {
                    "transport_name": "ussd_transport",
                    "event_type": "ack",
                    "event_id": six.text_type(uuid.uuid4()),
                    "sent_message_id": six.text_type(uuid.uuid4()),
                    "helper_metadata": {},
                    "routing_metadata": {},
                    "message_version": "20110921",
                    "timestamp": six.text_type(timezone.now()),
                    "transport_metadata": {},
                    "user_message_id": msg.external_id,
                    "message_type": "event"
                }
                callback_url = reverse('handlers.vumi_handler',
                                       args=['event', self.channel.uuid])
                self.client.post(callback_url,
                                 json.dumps(data),
                                 content_type="application/json")

                # it should be SENT now
                msg.refresh_from_db()
                self.assertEquals(SENT, msg.status)

                self.clear_cache()
        finally:
            settings.SEND_MESSAGES = False
Example #26
0
    def test_webhook_event_trim_task(self):
        sms = self.create_msg(contact=self.joe,
                              direction='I',
                              status='H',
                              text="I'm gonna pop some tags")
        self.setupChannel()
        now = timezone.now()

        with patch('requests.Session.send') as mock:
            mock.return_value = MockResponse(200, "Hello World")

            # trigger an event
            WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms,
                                           now)
            event = WebHookEvent.objects.get()

            five_hours_ago = timezone.now() - timedelta(hours=5)
            event.created_on = five_hours_ago
            event.save()

            with override_settings(SUCCESS_LOGS_TRIM_TIME=0):
                trim_webhook_event_task()
                self.assertTrue(WebHookEvent.objects.all())
                self.assertTrue(WebHookResult.objects.all())

            with override_settings(SUCCESS_LOGS_TRIM_TIME=12):
                trim_webhook_event_task()
                self.assertTrue(WebHookEvent.objects.all())
                self.assertTrue(WebHookResult.objects.all())

            with override_settings(SUCCESS_LOGS_TRIM_TIME=2):
                trim_webhook_event_task()
                self.assertFalse(WebHookEvent.objects.all())
                self.assertFalse(WebHookResult.objects.all())

            WebHookEvent.trigger_sms_event(WebHookEvent.TYPE_SMS_RECEIVED, sms,
                                           now)
            event = WebHookEvent.objects.get()

            five_hours_ago = timezone.now() - timedelta(hours=5)
            event.created_on = five_hours_ago
            event.status = FAILED
            event.save()

            with override_settings(ALL_LOGS_TRIM_TIME=0):
                trim_webhook_event_task()
                self.assertTrue(WebHookEvent.objects.all())
                self.assertTrue(WebHookResult.objects.all())

            with override_settings(ALL_LOGS_TRIM_TIME=12):
                trim_webhook_event_task()
                self.assertTrue(WebHookEvent.objects.all())
                self.assertTrue(WebHookResult.objects.all())

            with override_settings(ALL_LOGS_TRIM_TIME=2):
                trim_webhook_event_task()
                self.assertFalse(WebHookEvent.objects.all())
                self.assertFalse(WebHookResult.objects.all())
Example #27
0
    def test_refresh_templates_task(self, mock_get_api_templates,
                                    update_local_templates_mock, mock_health):
        TemplateTranslation.objects.all().delete()
        Channel.objects.all().delete()

        channel = self.create_channel(
            "D3",
            "360Dialog channel",
            address="1234",
            country="BR",
            config={
                Channel.CONFIG_BASE_URL: "https://example.com/whatsapp",
                Channel.CONFIG_AUTH_TOKEN: "123456789",
            },
        )

        self.login(self.admin)
        mock_get_api_templates.side_effect = [([], False),
                                              Exception("foo"),
                                              ([{
                                                  "name": "hello"
                                              }], True)]

        update_local_templates_mock.return_value = None

        mock_health.return_value = MockResponse(
            200, '{"meta": {"api_status": "stable", "version": "2.35.4"}}')

        # should skip if locked
        r = get_redis_connection()
        with r.lock("refresh_whatsapp_templates", timeout=1800):
            refresh_whatsapp_templates()
            self.assertEqual(0, mock_get_api_templates.call_count)
            self.assertEqual(0, update_local_templates_mock.call_count)

        # should skip if fail with API
        refresh_whatsapp_templates()

        mock_get_api_templates.assert_called_with(channel)
        self.assertEqual(1, mock_get_api_templates.call_count)
        self.assertEqual(0, update_local_templates_mock.call_count)

        # any exception
        refresh_whatsapp_templates()

        mock_get_api_templates.assert_called_with(channel)
        self.assertEqual(2, mock_get_api_templates.call_count)
        self.assertEqual(0, update_local_templates_mock.call_count)

        # now it should refresh
        refresh_whatsapp_templates()

        mock_get_api_templates.assert_called_with(channel)
        self.assertEqual(3, mock_get_api_templates.call_count)
        update_local_templates_mock.assert_called_once_with(
            channel, [{
                "name": "hello"
            }])
Example #28
0
    def test_connect(self):
        # add admin to beta group
        self.admin.groups.add(Group.objects.get(name="Beta"))

        url = reverse("classifiers.classifier_connect")
        response = self.client.get(url)
        self.assertRedirect(response, "/users/login/")

        self.login(self.admin)
        response = self.client.get(url)

        # should have url for claiming our type
        url = reverse("classifiers.types.bothub.connect")
        self.assertContains(response, url)

        response = self.client.get(url)
        post_data = response.context["form"].initial

        # will fail as we don't have anything filled out
        response = self.client.post(url, post_data)
        self.assertFormError(response, "form", "name", ["This field is required."])

        # ok, will everything out
        post_data["name"] = "Bothub Test Repository"
        post_data["access_token"] = "123456789"

        # can't connect
        with patch("requests.get") as mock_get:
            mock_get.return_value = MockResponse(400, '{ "error": "true" }')
            response = self.client.post(url, post_data)
            self.assertEqual(200, response.status_code)
            self.assertFalse(Classifier.objects.all())

            self.assertContains(response, "Unable to access bothub with credentials, please check and try again")

        # all good
        with patch("requests.get") as mock_get:
            mock_get.return_value = MockResponse(200, '{ "error": "false" }')
            response = self.client.post(url, post_data)
            self.assertEqual(302, response.status_code)

            c = Classifier.objects.get()
            self.assertEqual("Bothub Test Repository", c.name)
            self.assertEqual("bothub", c.classifier_type)
            self.assertEqual("123456789", c.config[BotHubType.CONFIG_ACCESS_TOKEN])
Example #29
0
    def test_download_media(self):
        self.org.connect_twilio("TEST_SID", "TEST_TOKEN", self.admin)
        self.org.save()

        with patch('requests.get') as response:
            mock1 = MockResponse(404, 'No such file')
            mock2 = MockResponse(200, 'Fake VCF Bits')
            mock2.add_header('Content-Type', 'text/x-vcard')
            mock2.add_header('Content-Disposition', 'inline')
            response.side_effect = (mock1, mock2)

            twilio_client = self.org.get_twilio_client()

            with patch('temba.orgs.models.Org.save_media') as mock_save_media:
                mock_save_media.return_value = 'SAVED'

                output = twilio_client.download_media(
                    'http://api.twilio.com/ASID/Media/SID')
                self.assertIsNotNone(output)
                self.assertEqual(output, 'text/x-vcard:SAVED')
                # saved_media was called with a file as first argument and the guessed extension as second argument
                self.assertIsInstance(mock_save_media.call_args_list[0][0][0],
                                      File)
                self.assertEqual(mock_save_media.call_args_list[0][0][1],
                                 'vcf')
Example #30
0
    def test_expression_migrate(self):
        with patch("requests.post") as mock_post:
            mock_post.return_value = MockResponse(200, '{"migrated": "@fields.age"}')
            migrated = get_client().expression_migrate("@contact.age")

            self.assertEqual("@fields.age", migrated)

            mock_post.assert_called_once_with(
                "http://*****:*****@contact.age"},
            )

            # in case of error just return original
            mock_post.return_value = MockResponse(422, '{"error": "bad isn\'t a thing"}')
            migrated = get_client().expression_migrate("@(bad)")

            self.assertEqual("@(bad)", migrated)
Example #31
0
    def test_get_transferto_response(self, mock_post_transferto):
        mock_post_transferto.return_value = MockResponse(200, "foo=allo\r\nbar=1,2,3\r\n")

        with self.settings(SEND_AIRTIME=True):
            response = self.airtime.get_transferto_response(action='command')
            self.assertContains(response, "foo=allo\r\nbar=1,2,3\r\n")

            mock_post_transferto.assert_called_once_with('mylogin', 'api_token', airtime_obj=self.airtime,
                                                         action='command')
Example #32
0
    def test_download_media(self):
        self.org.connect_twilio("TEST_SID", "TEST_TOKEN", self.admin)
        self.org.save()

        with patch("requests.get") as response:
            mock1 = MockResponse(404, "No such file")
            mock2 = MockResponse(200, "Fake VCF Bits")
            mock2.add_header("Content-Type", "text/x-vcard")
            mock2.add_header("Content-Disposition", "inline")
            response.side_effect = (mock1, mock2)

            twilio_client = self.org.get_twilio_client()

            with patch("temba.orgs.models.Org.save_media") as mock_save_media:
                mock_save_media.return_value = "SAVED"

                output = twilio_client.download_media("http://api.twilio.com/ASID/Media/SID")
                self.assertIsNotNone(output)
                self.assertEqual(output, "text/x-vcard:SAVED")
                # saved_media was called with a file as first argument and the guessed extension as second argument
                self.assertIsInstance(mock_save_media.call_args_list[0][0][0], File)
                self.assertEqual(mock_save_media.call_args_list[0][0][1], "vcf")
Example #33
0
    def test_ivr_recording(self):

        # create our ivr setup
        self.org.connect_twilio("TEST_SID", "TEST_TOKEN", self.admin)
        self.org.save()
        self.import_file("capture_recording")
        flow = Flow.objects.filter(name="Capture Recording").first()

        # start our flow
        contact = self.create_contact("Chuck D", number="+13603621737")
        flow.start([], [contact])
        call = IVRCall.objects.filter(direction=OUTGOING).first()

        # after a call is picked up, twilio will call back to our server
        post_data = dict(CallSid="CallSid", CallStatus="in-progress", CallDuration=20)
        response = self.client.post(reverse("ivr.ivrcall_handle", args=[call.pk]), post_data)
        self.assertContains(response, "<Say>Please make a recording after the tone.</Say>")

        # simulate the caller making a recording and then hanging up, first they'll give us the
        # recording (they give us a call status of completed at the same time)
        from temba.tests import MockResponse

        with patch("requests.get") as response:
            mock1 = MockResponse(404, "No such file")
            mock2 = MockResponse(200, "Fake Recording Bits")
            mock2.add_header("Content-Type", "audio/x-wav")
            response.side_effect = (mock1, mock2)

            self.client.post(
                reverse("ivr.ivrcall_handle", args=[call.pk]),
                dict(
                    CallStatus="completed",
                    Digits="hangup",
                    RecordingUrl="http://api.twilio.com/ASID/Recordings/SID",
                    RecordingSid="FAKESID",
                ),
            )

        # we should have captured the recording, and ended the call
        call = IVRCall.objects.get(pk=call.pk)
        self.assertEquals(COMPLETED, call.status)

        # twilio will also send us a final completion message with the call duration (status of completed again)
        self.client.post(reverse("ivr.ivrcall_handle", args=[call.pk]), dict(CallStatus="completed", CallDuration="15"))

        call = IVRCall.objects.get(pk=call.pk)
        self.assertEquals(COMPLETED, call.status)
        self.assertEquals(15, call.duration)

        messages = Msg.all_messages.filter(msg_type=IVR).order_by("pk")
        self.assertEquals(4, messages.count())
        self.assertEquals(4, self.org.get_credits_used())

        # we should have played a recording from the contact back to them
        outbound_msg = messages[1]
        self.assertTrue(outbound_msg.media.startswith("audio/x-wav:https://"))
        self.assertTrue(outbound_msg.media.endswith(".wav"))
        self.assertTrue(outbound_msg.text.startswith("https://"))
        self.assertTrue(outbound_msg.text.endswith(".wav"))

        media_msg = messages[2]
        self.assertTrue(media_msg.media.startswith("audio/x-wav:https://"))
        self.assertTrue(media_msg.media.endswith(".wav"))
        self.assertTrue(media_msg.text.startswith("https://"))
        self.assertTrue(media_msg.text.endswith(".wav"))

        (host, directory, filename) = media_msg.media.rsplit("/", 2)
        recording = "%s/%s/%s/media/%s/%s" % (
            settings.MEDIA_ROOT,
            settings.STORAGE_ROOT_DIR,
            self.org.pk,
            directory,
            filename,
        )
        self.assertTrue(os.path.isfile(recording))

        from temba.flows.models import FlowStep

        steps = FlowStep.objects.all()
        self.assertEquals(4, steps.count())

        # each of our steps should have exactly one message
        for step in steps:
            self.assertEquals(1, step.messages.all().count(), msg="Step '%s' does not have excatly one message" % step)

        # each message should have exactly one step
        for msg in messages:
            self.assertEquals(
                1, msg.steps.all().count(), msg="Message '%s' is not attached to exaclty one step" % msg.text
            )
Example #34
0
    def test_ivr_recording(self):

        # create our ivr setup
        self.org.connect_twilio("TEST_SID", "TEST_TOKEN")
        self.org.save()
        self.import_file('capture-recording')
        flow = Flow.objects.filter(name='Capture Recording').first()

        # start our flow
        contact = self.create_contact('Chuck D', number='+13603621737')
        flow.start([], [contact])
        call = IVRCall.objects.filter(direction=OUTGOING).first()

        # after a call is picked up, twilio will call back to our server
        post_data = dict(CallSid='CallSid', CallStatus='in-progress', CallDuration=20)
        response = self.client.post(reverse('ivr.ivrcall_handle', args=[call.pk]), post_data)
        self.assertContains(response, '<Say>Please make a recording after the tone.</Say>')

        # simulate the caller making a recording and then hanging up, first they'll give us the
        # recording (they give us a call status of completed at the same time)
        from temba.tests import MockResponse
        with patch('requests.get') as response:
            mock = MockResponse(200, 'Fake Recording Bits')
            mock.add_header('Content-Type', 'audio/x-wav')
            response.return_value = mock

            self.client.post(reverse('ivr.ivrcall_handle', args=[call.pk]),
                             dict(CallStatus='completed',
                                  Digits='hangup',
                                  RecordingUrl='http://api.twilio.com/ASID/Recordings/SID',
                                  RecordingSid='FAKESID'))

        # we should have captured the recording, and ended the call
        call = IVRCall.objects.get(pk=call.pk)
        self.assertEquals(COMPLETED, call.status)

        # twilio will also send us a final completion message with the call duration (status of completed again)
        self.client.post(reverse('ivr.ivrcall_handle', args=[call.pk]),
                         dict(CallStatus='completed', CallDuration='15'))

        call = IVRCall.objects.get(pk=call.pk)
        self.assertEquals(COMPLETED, call.status)
        self.assertEquals(15, call.duration)

        messages = Msg.all_messages.filter(msg_type=IVR).order_by('pk')
        self.assertEquals(4, messages.count())
        self.assertEquals(4, self.org.get_credits_used())

        # we should have played a recording from the contact back to them
        outbound_msg = messages[1]
        self.assertTrue(outbound_msg.media.startswith('audio/x-wav:https://'))
        self.assertTrue(outbound_msg.media.endswith('.wav'))
        self.assertTrue(outbound_msg.text.startswith('https://'))
        self.assertTrue(outbound_msg.text.endswith('.wav'))

        media_msg = messages[2]
        self.assertTrue(media_msg.media.startswith('audio/x-wav:https://'))
        self.assertTrue(media_msg.media.endswith('.wav'))
        self.assertTrue(media_msg.text.startswith('https://'))
        self.assertTrue(media_msg.text.endswith('.wav'))

        (host, directory, filename) = media_msg.media.rsplit('/', 2)
        recording = '%s/%s/%s/media/%s/%s' % (settings.MEDIA_ROOT, settings.STORAGE_ROOT_DIR,
                                              self.org.pk, directory, filename)
        self.assertTrue(os.path.isfile(recording))

        from temba.flows.models import FlowStep
        steps = FlowStep.objects.all()
        self.assertEquals(4, steps.count())

        # each of our steps should have exactly one message
        for step in steps:
            self.assertEquals(1, step.messages.all().count(), msg="Step '%s' does not have excatly one message" % step)

        # each message should have exactly one step
        for msg in messages:
            self.assertEquals(1, msg.steps.all().count(), msg="Message '%s' is not attached to exaclty one step" % msg.text)