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 send(self, message, contact=None): if not contact: contact = self.contact if contact.is_test: Contact.set_simulation(True) incoming = self.create_msg(direction=INCOMING, contact=contact, text=message) Flow.find_and_handle(incoming) return Msg.all_messages.filter(response_to=incoming).order_by('pk').first()
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 send(self, message, contact=None): if not contact: contact = self.contact if contact.is_test: Contact.set_simulation(True) incoming = self.create_msg(direction=INCOMING, contact=contact, text=message) Flow.find_and_handle(incoming) return Msg.all_messages.filter( response_to=incoming).order_by('pk').first()
def send(self, message, contact=None): if not contact: contact = self.contact if contact.is_test: Contact.set_simulation(True) incoming = self.create_msg(direction=INCOMING, contact=contact, text=message) # evaluate the inbound message against our triggers first from temba.triggers.models import Trigger if not Trigger.find_and_handle(incoming): Flow.find_and_handle(incoming) return Msg.objects.filter(response_to=incoming).order_by('pk').first()
def send(self, message, contact=None): if not contact: contact = self.contact if contact.is_test: Contact.set_simulation(True) incoming = self.create_msg(direction=INCOMING, contact=contact, text=message) # evaluate the inbound message against our triggers first from temba.triggers.models import Trigger if not Trigger.find_and_handle(incoming): Flow.find_and_handle(incoming) return Msg.objects.filter(response_to=incoming).order_by('pk').first()
def send_message( self, flow, message, restart_participants=False, contact=None, initiate_flow=False, assert_reply=True, assert_handle=True, ): """ Starts the flow, sends the message, returns the reply """ if not contact: contact = self.contact try: if contact.is_test: Contact.set_simulation(True) incoming = self.create_msg( direction=INCOMING, contact=contact, contact_urn=contact.get_urn(), text=message ) # start the flow if initiate_flow: flow.start( groups=[], contacts=[contact], restart_participants=restart_participants, start_msg=incoming ) else: flow.start(groups=[], contacts=[contact], restart_participants=restart_participants) (handled, msgs) = Flow.find_and_handle(incoming) Msg.mark_handled(incoming) if assert_handle: self.assertTrue(handled, "'%s' did not handle message as expected" % flow.name) else: self.assertFalse(handled, "'%s' handled message, was supposed to ignore" % flow.name) # our message should have gotten a reply if assert_reply: replies = Msg.objects.filter(response_to=incoming).order_by("pk") self.assertGreaterEqual(len(replies), 1) if len(replies) == 1: self.assertEqual(contact, replies.first().contact) return replies.first().text # if it's more than one, send back a list of replies return [reply.text for reply in replies] else: # assert we got no reply replies = Msg.objects.filter(response_to=incoming).order_by("pk") self.assertFalse(replies) return None finally: Contact.set_simulation(False)
def send_message(self, flow, message, restart_participants=False, contact=None, initiate_flow=False, assert_reply=True, assert_handle=True): """ Starts the flow, sends the message, returns the reply """ if not contact: contact = self.contact try: if contact.is_test: Contact.set_simulation(True) incoming = self.create_msg(direction=INCOMING, contact=contact, text=message) # start the flow if initiate_flow: flow.start(groups=[], contacts=[contact], restart_participants=restart_participants, start_msg=incoming) else: flow.start(groups=[], contacts=[contact], restart_participants=restart_participants) handled = Flow.find_and_handle(incoming) Msg.mark_handled(incoming) if assert_handle: self.assertTrue(handled, "'%s' did not handle message as expected" % flow.name) else: self.assertFalse(handled, "'%s' handled message, was supposed to ignore" % flow.name) # our message should have gotten a reply if assert_reply: replies = Msg.objects.filter(response_to=incoming).order_by('pk') self.assertGreaterEqual(len(replies), 1) if len(replies) == 1: self.assertEquals(contact, replies.first().contact) return replies.first().text # if it's more than one, send back a list of replies return [reply.text for reply in replies] else: # assert we got no reply replies = Msg.objects.filter(response_to=incoming).order_by('pk') self.assertFalse(replies) return None finally: Contact.set_simulation(False)
def test_rule_first_ivr_flow(self): # connect it and check our client is configured self.org.connect_twilio("TEST_SID", "TEST_TOKEN") self.org.save() # import an ivr flow self.import_file('rule-first-ivr') flow = Flow.objects.filter(name='Rule First IVR').first() user_settings = self.admin.get_settings() user_settings.tel = '+18005551212' user_settings.save() # start our flow eric = self.create_contact('Eric Newcomer', number='+13603621737') eric.is_test = True eric.save() Contact.set_simulation(True) flow.start([], [eric]) # should be using the usersettings number in test mode self.assertEquals('Placing test call to +1 800-555-1212', ActionLog.objects.all().first().text) # we should have an outbound ivr call now call = IVRCall.objects.filter(direction=OUTGOING).first() self.assertEquals(0, call.get_duration()) self.assertIsNotNone(call) self.assertEquals('CallSid', call.external_id) # 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>Thanks for calling!</Say>') # make sure a message from the person on the call goes to the # inbox since our flow doesn't handle text messages msg = self.create_msg(direction='I', contact=eric, text="message during phone call") self.assertFalse(Flow.find_and_handle(msg))
def handle_message(msg): """ Only used for testing to approximate how mailroom handles a message """ from temba.flows.models import Flow from temba.msgs.models import Msg from temba.triggers.models import Trigger if msg.contact.is_blocked: msg.visibility = Msg.VISIBILITY_ARCHIVED msg.save(update_fields=["visibility", "modified_on"]) else: handled = Trigger.find_and_handle(msg) if not handled: handled, msgs = Flow.find_and_handle(msg) if not handled: Trigger.catch_triggers(msg, Trigger.TYPE_CATCH_ALL, msg.channel) mark_handled(msg)
def test_rule_first_ivr_flow(self): # connect it and check our client is configured self.org.connect_twilio("TEST_SID", "TEST_TOKEN") self.org.save() # import an ivr flow self.import_file('rule-first-ivr') flow = Flow.objects.filter(name='Rule First IVR').first() user_settings = self.admin.get_settings() user_settings.tel = '+18005551212' user_settings.save() # start our flow eric = self.create_contact('Eric Newcomer', number='+13603621737') eric.is_test = True eric.save() Contact.set_simulation(True) flow.start([], [eric]) # should be using the usersettings number in test mode self.assertEquals('Placing test call to +1 800-555-1212', ActionLog.objects.all().first().text) # we should have an outbound ivr call now call = IVRCall.objects.filter(direction=OUTGOING).first() self.assertEquals(0, call.get_duration()) self.assertIsNotNone(call) self.assertEquals('CallSid', call.external_id) # 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>Thanks for calling!</Say>') # make sure a message from the person on the call goes to the # inbox since our flow doesn't handle text messages msg = self.create_msg(direction='I', contact=eric, text="message during phone call") self.assertFalse(Flow.find_and_handle(msg))
def test_rule_first_ivr_flow(self): # connect it and check our client is configured self.org.connect_twilio("TEST_SID", "TEST_TOKEN", self.admin) self.org.save() # import an ivr flow flow = self.get_flow("rule_first_ivr") user_settings = self.admin.get_settings() user_settings.tel = "+18005551212" user_settings.save() # start our flow test_contact = Contact.get_test_contact(self.admin) Contact.set_simulation(True) flow.start([], [test_contact]) # should be using the usersettings number in test mode self.assertEquals("Placing test call to +1 800-555-1212", ActionLog.objects.all().first().text) # we should have an outbound ivr call now call = IVRCall.objects.filter(direction=OUTGOING).first() self.assertEquals(0, call.get_duration()) self.assertIsNotNone(call) self.assertEquals("CallSid", call.external_id) # 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>Thanks for calling!</Say>") # make sure a message from the person on the call goes to the # inbox since our flow doesn't handle text messages msg = self.create_msg(direction="I", contact=test_contact, text="message during phone call") self.assertFalse(Flow.find_and_handle(msg))
def test_flow_event(self, mock_send): self.setupChannel() org = self.channel.org org.save() flow = self.get_flow("color") # replace our uuid of 4 with the right thing actionset = ActionSet.objects.get(x=4) actionset.actions = [WebhookAction(str(uuid4()), org.get_webhook_url()).as_json()] actionset.save() # 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", attachments=["image/jpeg:http://s3.com/text.jpg", "audio/mp4:http://s3.com/text.mp4"], ) mock_send.return_value = MockResponse(200, "{}") Flow.find_and_handle(sms) # should have one event created event = WebHookEvent.objects.get() self.assertEqual("C", event.status) self.assertEqual(1, event.try_count) self.assertFalse(event.next_attempt) result = WebHookResult.objects.get() self.assertIn("successfully", result.message) self.assertEqual(200, result.status_code) self.assertEqual(self.joe, result.contact) self.assertTrue(mock_send.called) args = mock_send.call_args_list[0][0] prepared_request = args[0] self.assertIn(self.channel.org.get_webhook_url(), prepared_request.url) data = json.loads(prepared_request.body) self.assertEqual(data["channel"], {"uuid": str(self.channel.uuid), "name": self.channel.name}) self.assertEqual( data["contact"], {"uuid": str(self.joe.uuid), "name": self.joe.name, "urn": str(self.joe.get_urn("tel"))} ) self.assertEqual(data["flow"], {"uuid": str(flow.uuid), "name": flow.name, "revision": 1}) self.assertEqual( data["input"], { "urn": "tel:+250788123123", "text": "Mauve", "attachments": ["image/jpeg:http://s3.com/text.jpg", "audio/mp4:http://s3.com/text.mp4"], }, ) self.assertEqual( data["results"], { "color": { "category": "Other", "node_uuid": matchers.UUID4String(), "name": "color", "value": "Mauve\nhttp://s3.com/text.jpg\nhttp://s3.com/text.mp4", "created_on": matchers.ISODate(), "input": "Mauve\nhttp://s3.com/text.jpg\nhttp://s3.com/text.mp4", } }, )
def test_flow_event(self, mock_send): self.setupChannel() org = self.channel.org org.save() flow = self.create_flow(definition=self.COLOR_FLOW_DEFINITION) # 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() # 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", attachments=[ "image/jpeg:http://s3.com/text.jpg", "audio/mp4:http://s3.com/text.mp4" ]) mock_send.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.assertIn("successfully", result.message) self.assertEquals(200, result.status_code) self.assertEqual(self.joe, result.contact) self.assertTrue(mock_send.called) args = mock_send.call_args_list[0][0] prepared_request = args[0] self.assertIn(self.channel.org.get_webhook_url(), prepared_request.url) data = parse_qs(prepared_request.body) self.assertEqual(data['channel'], [str(self.channel.id)]) self.assertEqual(data['channel_uuid'], [self.channel.uuid]) self.assertEqual(data['step'], [actionset.uuid]) self.assertEqual(data['text'], ["Mauve"]) self.assertEqual(data['attachments'], ["http://s3.com/text.jpg", "http://s3.com/text.mp4"]) self.assertEqual(data['flow'], [str(flow.id)]) self.assertEqual(data['flow_uuid'], [flow.uuid]) self.assertEqual(data['contact'], [self.joe.uuid]) self.assertEqual(data['contact_name'], [self.joe.name]) self.assertEqual(data['urn'], [six.text_type(self.joe.get_urn('tel'))]) 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_ivr_flow(self): # should be able to create an ivr flow self.assertTrue(self.org.supports_ivr()) self.assertTrue(self.admin.groups.filter(name="Beta")) self.assertContains(self.client.get(reverse('flows.flow_create')), 'Phone Call') # no twilio config yet self.assertFalse(self.org.is_connected_to_twilio()) self.assertIsNone(self.org.get_twilio_client()) # connect it and check our client is configured self.org.connect_twilio("TEST_SID", "TEST_TOKEN", self.admin) self.org.save() self.assertTrue(self.org.is_connected_to_twilio()) self.assertIsNotNone(self.org.get_twilio_client()) # import an ivr flow self.import_file('call_me_maybe') # make sure our flow is there as expected flow = Flow.objects.filter(name='Call me maybe').first() self.assertEquals( 'callme', flow.triggers.filter(trigger_type='K').first().keyword) user_settings = self.admin.get_settings() user_settings.tel = '+18005551212' user_settings.save() # start our flow as a test contact test_contact = Contact.get_test_contact(self.admin) Contact.set_simulation(True) flow.start([], [test_contact]) call = IVRCall.objects.filter(direction=OUTGOING).first() # should be using the usersettings number in test mode self.assertEquals('Placing test call to +1 800-555-1212', ActionLog.objects.all().first().text) # explicitly hanging up on a test call should remove it call.update_status('in-progress', 0) call.save() IVRCall.hangup_test_call(flow) self.assertTrue(IVRCall.objects.filter(pk=call.pk).first()) ActionLog.objects.all().delete() IVRCall.objects.all().delete() # now pretend we are a normal caller eric = self.create_contact('Eric Newcomer', number='+13603621737') Contact.set_simulation(False) flow.start([], [eric], restart_participants=True) # we should have an outbound ivr call now call = IVRCall.objects.filter(direction=OUTGOING).first() self.assertEquals(0, call.get_duration()) self.assertIsNotNone(call) self.assertEquals('CallSid', call.external_id) # 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>Would you like me to call you? Press one for yes, two for no, or three for maybe.</Say>' ) self.assertEquals(1, Msg.all_messages.filter(msg_type=IVR).count()) self.assertEquals(1, self.org.get_credits_used()) # make sure a message from the person on the call goes to the # inbox since our flow doesn't handle text messages msg = self.create_msg(direction='I', contact=eric, text="message during phone call") self.assertFalse(Flow.find_and_handle(msg)) # updated our status and duration accordingly call = IVRCall.objects.get(pk=call.pk) self.assertEquals(20, call.duration) self.assertEquals(IN_PROGRESS, call.status) # don't press any numbers, but # instead response = self.client.post( reverse('ivr.ivrcall_handle', args=[call.pk]) + "?empty=1", dict()) self.assertContains(response, '<Say>Press one, two, or three. Thanks.</Say>') self.assertEquals(4, self.org.get_credits_used()) # press the number 4 (unexpected) response = self.client.post( reverse('ivr.ivrcall_handle', args=[call.pk]), dict(Digits=4)) # our inbound message should be handled msg = Msg.current_messages.filter( text='4', msg_type=IVR).order_by('-created_on').first() self.assertEqual('H', msg.status) self.assertContains(response, '<Say>Press one, two, or three. Thanks.</Say>') self.assertEquals(6, self.org.get_credits_used()) # two more messages, one inbound and it's response self.assertEquals(5, Msg.all_messages.filter(msg_type=IVR).count()) # now let's have them press the number 3 (for maybe) response = self.client.post( reverse('ivr.ivrcall_handle', args=[call.pk]), dict(Digits=3)) self.assertContains(response, '<Say>This might be crazy.</Say>') messages = Msg.all_messages.filter(msg_type=IVR).order_by('pk') self.assertEquals(7, messages.count()) self.assertEquals(8, self.org.get_credits_used()) for msg in messages: self.assertEquals(1, msg.steps.all().count(), msg="Message '%s' not attached to step" % msg.text) # twilio would then disconnect the user and notify us of a completed call self.client.post(reverse('ivr.ivrcall_handle', args=[call.pk]), dict(CallStatus='completed')) call = IVRCall.objects.get(pk=call.pk) self.assertEquals(COMPLETED, call.status) self.assertFalse(FlowRun.objects.filter(call=call).first().is_active) # simulation gets flipped off by middleware, and this unhandled message doesn't flip it back on self.assertFalse(Contact.get_simulation()) # also shouldn't have any ActionLogs for non-test users self.assertEquals(0, ActionLog.objects.all().count()) self.assertEquals(1, flow.get_completed_runs()) # should still have no active runs self.assertEquals(0, FlowRun.objects.filter(is_active=True).count()) # and we've exited the flow step = FlowStep.objects.all().order_by('-pk').first() self.assertTrue(step.left_on) # test other our call status mappings with twilio def test_status_update(call_to_update, twilio_status, temba_status): call_to_update.update_status(twilio_status, 0) call_to_update.save() self.assertEquals(temba_status, IVRCall.objects.get(pk=call_to_update.pk).status) test_status_update(call, 'queued', QUEUED) test_status_update(call, 'ringing', RINGING) test_status_update(call, 'canceled', CANCELED) test_status_update(call, 'busy', BUSY) test_status_update(call, 'failed', FAILED) test_status_update(call, 'no-answer', NO_ANSWER) FlowStep.objects.all().delete() IVRCall.objects.all().delete() # try sending callme trigger from temba.msgs.models import INCOMING msg = self.create_msg(direction=INCOMING, contact=eric, text="callme") # make sure if we are started with a message we still create a normal voice run flow.start([], [eric], restart_participants=True, start_msg=msg) # we should have an outbound ivr call now, and no steps yet call = IVRCall.objects.filter(direction=OUTGOING).first() self.assertIsNotNone(call) self.assertEquals(0, FlowStep.objects.all().count()) # 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 flow steps (the outgoing messages, and the step to handle the response) steps = FlowStep.objects.all().order_by('pk') # the first step has exactly one message which is an outgoing IVR message self.assertEquals(1, steps.first().messages.all().count()) self.assertEquals( 1, steps.first().messages.filter(direction=OUTGOING, msg_type=IVR).count()) # the next step shouldn't have any messages yet since they haven't pressed anything self.assertEquals(0, steps[1].messages.all().count()) # try updating our status to completed for a test contact Contact.set_simulation(True) flow.start([], [test_contact]) call = IVRCall.objects.filter( direction=OUTGOING).order_by('-pk').first() call.update_status('completed', 30) call.save() call.refresh_from_db() self.assertEqual(ActionLog.objects.all().order_by('-pk').first().text, 'Call ended.') self.assertEqual(call.duration, 30) # now look at implied duration call.update_status('in-progress', None) call.save() call.refresh_from_db() self.assertIsNotNone(call.get_duration())
def test_flow_event(self, mock_send): self.setupChannel() org = self.channel.org org.save() flow = self.get_flow('color') # replace our uuid of 4 with the right thing actionset = ActionSet.objects.get(x=4) actionset.set_actions_dict( [WebhookAction(str(uuid4()), org.get_webhook_url()).as_json()]) actionset.save() # 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", attachments=[ "image/jpeg:http://s3.com/text.jpg", "audio/mp4:http://s3.com/text.mp4" ]) mock_send.return_value = MockResponse(200, "{}") Flow.find_and_handle(sms) # should have one event created event = WebHookEvent.objects.get() self.assertEqual('C', event.status) self.assertEqual(1, event.try_count) self.assertFalse(event.next_attempt) result = WebHookResult.objects.get() self.assertIn("successfully", result.message) self.assertEqual(200, result.status_code) self.assertEqual(self.joe, result.contact) self.assertTrue(mock_send.called) args = mock_send.call_args_list[0][0] prepared_request = args[0] self.assertIn(self.channel.org.get_webhook_url(), prepared_request.url) data = json.loads(prepared_request.body) self.assertEqual(data['channel'], { 'uuid': str(self.channel.uuid), 'name': self.channel.name }) self.assertEqual( data['contact'], { 'uuid': str(self.joe.uuid), 'name': self.joe.name, 'urn': six.text_type(self.joe.get_urn('tel')) }) self.assertEqual(data['flow'], { 'uuid': str(flow.uuid), 'name': flow.name }) self.assertEqual( data['input'], { 'urn': 'tel:+250788123123', 'text': "Mauve", 'attachments': [ "image/jpeg:http://s3.com/text.jpg", "audio/mp4:http://s3.com/text.mp4" ] }) self.assertEqual(data['results']['color']['category'], 'Other') self.assertEqual(data['results']['color']['name'], 'color') self.assertEqual(data['results']['color']['value'], 'Mauve') self.assertEqual(data['results']['color']['input'], 'Mauve')
def test_flow_event(self, mock_send): self.setupChannel() org = self.channel.org org.save() flow = self.get_flow("color") # replace our uuid of 4 with the right thing actionset = ActionSet.objects.get(x=4) actionset.actions = [ WebhookAction(str(uuid4()), org.get_webhook_url()).as_json() ] actionset.save() # 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", attachments=[ "image/jpeg:http://s3.com/text.jpg", "audio/mp4:http://s3.com/text.mp4" ], ) mock_send.return_value = MockResponse(200, "{}") Flow.find_and_handle(sms) # should have one event created event = WebHookEvent.objects.get() self.assertEqual("C", event.status) self.assertEqual(1, event.try_count) self.assertFalse(event.next_attempt) result = WebHookResult.objects.get() self.assertIn("successfully", result.message) self.assertEqual(200, result.status_code) self.assertEqual(self.joe, result.contact) self.assertTrue(mock_send.called) args = mock_send.call_args_list[0][0] prepared_request = args[0] self.assertIn(self.channel.org.get_webhook_url(), prepared_request.url) data = json.loads(prepared_request.body) self.assertEqual(data["channel"], { "uuid": str(self.channel.uuid), "name": self.channel.name }) self.assertEqual( data["contact"], { "uuid": str(self.joe.uuid), "name": self.joe.name, "urn": str(self.joe.get_urn("tel")) }) self.assertEqual(data["flow"], { "uuid": str(flow.uuid), "name": flow.name, "revision": 1 }) self.assertEqual( data["input"], { "urn": "tel:+250788123123", "text": "Mauve", "attachments": [ "image/jpeg:http://s3.com/text.jpg", "audio/mp4:http://s3.com/text.mp4" ], }, ) self.assertEqual( data["results"], { "color": { "category": "Other", "node_uuid": matchers.UUID4String(), "name": "color", "value": "Mauve\nhttp://s3.com/text.jpg\nhttp://s3.com/text.mp4", "created_on": matchers.ISODate(), "input": "Mauve\nhttp://s3.com/text.jpg\nhttp://s3.com/text.mp4", } }, )
def test_ivr_flow(self): # should be able to create an ivr flow self.assertTrue(self.org.supports_ivr()) self.assertTrue(self.admin.groups.filter(name="Beta")) self.assertContains(self.client.get(reverse('flows.flow_create')), 'Phone Call') # no twilio config yet self.assertFalse(self.org.is_connected_to_twilio()) self.assertIsNone(self.org.get_twilio_client()) # connect it and check our client is configured self.org.connect_twilio("TEST_SID", "TEST_TOKEN") self.org.save() self.assertTrue(self.org.is_connected_to_twilio()) self.assertIsNotNone(self.org.get_twilio_client()) # import an ivr flow self.import_file('call-me-maybe') # make sure our flow is there as expected flow = Flow.objects.filter(name='Call me maybe').first() self.assertEquals('callme', flow.triggers.filter(trigger_type='K').first().keyword) user_settings = self.admin.get_settings() user_settings.tel = '+18005551212' user_settings.save() # start our flow as a test contact test_contact = Contact.get_test_contact(self.admin) Contact.set_simulation(True) flow.start([], [test_contact]) call = IVRCall.objects.filter(direction=OUTGOING).first() # should be using the usersettings number in test mode self.assertEquals('Placing test call to +1 800-555-1212', ActionLog.objects.all().first().text) # explicitly hanging up on a test call should remove it call.update_status('in-progress', 0) call.save() IVRCall.hangup_test_call(flow) self.assertIsNone(IVRCall.objects.filter(pk=call.pk).first()) ActionLog.objects.all().delete() IVRCall.objects.all().delete() # now pretend we are a normal caller eric = self.create_contact('Eric Newcomer', number='+13603621737') Contact.set_simulation(False) flow.start([], [eric], restart_participants=True) # we should have an outbound ivr call now call = IVRCall.objects.filter(direction=OUTGOING).first() self.assertEquals(0, call.get_duration()) self.assertIsNotNone(call) self.assertEquals('CallSid', call.external_id) # 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>Would you like me to call you? Press one for yes, two for no, or three for maybe.</Say>') self.assertEquals(1, Msg.all_messages.filter(msg_type=IVR).count()) self.assertEquals(1, self.org.get_credits_used()) # make sure a message from the person on the call goes to the # inbox since our flow doesn't handle text messages msg = self.create_msg(direction='I', contact=eric, text="message during phone call") self.assertFalse(Flow.find_and_handle(msg)) # updated our status and duration accordingly call = IVRCall.objects.get(pk=call.pk) self.assertEquals(20, call.duration) self.assertEquals(IN_PROGRESS, call.status) # press the number 4 (unexpected) response = self.client.post(reverse('ivr.ivrcall_handle', args=[call.pk]), dict(Digits=4)) self.assertContains(response, '<Say>Press one, two, or three. Thanks.</Say>') self.assertEquals(4, self.org.get_credits_used()) # two more messages, one inbound and it's response self.assertEquals(3, Msg.all_messages.filter(msg_type=IVR).count()) # now let's have them press the number 3 (for maybe) response = self.client.post(reverse('ivr.ivrcall_handle', args=[call.pk]), dict(Digits=3)) self.assertContains(response, '<Say>This might be crazy.</Say>') messages = Msg.all_messages.filter(msg_type=IVR).order_by('pk') self.assertEquals(5, messages.count()) self.assertEquals(6, self.org.get_credits_used()) for msg in messages: self.assertEquals(1, msg.steps.all().count(), msg="Message '%s' not attached to step" % msg.text) # twilio would then disconnect the user and notify us of a completed call self.client.post(reverse('ivr.ivrcall_handle', args=[call.pk]), dict(CallStatus='completed')) call = IVRCall.objects.get(pk=call.pk) self.assertEquals(COMPLETED, call.status) self.assertFalse(FlowRun.objects.filter(call=call).first().is_active) # simulation gets flipped off by middleware, and this unhandled message doesn't flip it back on self.assertFalse(Contact.get_simulation()) # also shouldn't have any ActionLogs for non-test users self.assertEquals(0, ActionLog.objects.all().count()) self.assertEquals(1, flow.get_completed_runs()) # should still have no active runs self.assertEquals(0, FlowRun.objects.filter(is_active=True).count()) # and we've exited the flow step = FlowStep.objects.all().order_by('-pk').first() self.assertTrue(step.left_on) # test other our call status mappings with twilio def test_status_update(call_to_update, twilio_status, temba_status): call_to_update.update_status(twilio_status, 0) call_to_update.save() self.assertEquals(temba_status, IVRCall.objects.get(pk=call_to_update.pk).status) test_status_update(call, 'queued', QUEUED) test_status_update(call, 'ringing', RINGING) test_status_update(call, 'canceled', CANCELED) test_status_update(call, 'busy', BUSY) test_status_update(call, 'failed', FAILED) test_status_update(call, 'no-answer', NO_ANSWER) FlowStep.objects.all().delete() IVRCall.objects.all().delete() # try sending callme trigger from temba.msgs.models import INCOMING msg = self.create_msg(direction=INCOMING, contact=eric, text="callme") # make sure if we are started with a message we still create a normal voice run flow.start([], [eric], restart_participants=True, start_msg=msg) # we should have an outbound ivr call now, and no steps yet call = IVRCall.objects.filter(direction=OUTGOING).first() self.assertIsNotNone(call) self.assertEquals(0, FlowStep.objects.all().count()) # 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 flow steps (the outgoing messages, and the step to handle the response) steps = FlowStep.objects.all().order_by('pk') # the first step has exactly one message which is an outgoing IVR message self.assertEquals(1, steps.first().messages.all().count()) self.assertEquals(1, steps.first().messages.filter(direction=OUTGOING, msg_type=IVR).count()) # the next step shouldn't have any messages yet since they haven't pressed anything self.assertEquals(0, steps[1].messages.all().count()) # try updating our status to completed for a test contact Contact.set_simulation(True) flow.start([], [test_contact]) call = IVRCall.objects.filter(direction=OUTGOING).order_by('-pk').first() call.update_status('completed', 30) call.save() call.refresh_from_db() self.assertEqual(ActionLog.objects.all().order_by('-pk').first().text, 'Call ended.') self.assertEqual(call.duration, 30) # now look at implied duration call.update_status('in-progress', None) call.save() call.refresh_from_db() self.assertIsNotNone(call.get_duration())