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")
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())
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 })
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())
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"]))
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"} )
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" }, }, )
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))
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"}, )
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"}, )
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'])
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" )
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)
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" }, )
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()
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)
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
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)
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)
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", }, )
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])
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'})
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")
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")
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
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())
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" }])
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])
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')
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)
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')
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")
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 )
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)