def testSync(self): extra_number = PhoneNumber.objects.create( domain=self.domain, owner_doc_type='X', owner_id='owner1', phone_number='999123', verified=True, pending_verification=False, is_two_way=True ) user = self.mobile_worker1 self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 1) user.phone_numbers = ['9990001'] user.save() self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 2) self.assertPhoneEntries(user, ['9990001']) before = user.get_phone_entries()['9990001'] user.phone_numbers = ['9990001', '9990002'] user.save() self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 3) self.assertPhoneEntries(user, ['9990001', '9990002']) after = user.get_phone_entries()['9990001'] self.assertEqual(before.pk, after.pk) user.phone_numbers = ['9990002'] user.save() self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 2) self.assertPhoneEntries(user, ['9990002']) self.assertEqual(PhoneNumber.get_two_way_number('999123'), extra_number)
def test_sms(self): from corehq.apps.sms.models import PhoneNumber, MessagingEvent, MessagingSubEvent expected_object_counts = Counter({PhoneNumber: 1, MessagingEvent: 1, MessagingSubEvent: 1}) phone_number = PhoneNumber( domain=self.domain_name, owner_doc_type='CommCareCase', owner_id='fake-owner-id1', phone_number='99912341234', backend_id=None, ivr_backend_id=None, verified=True, is_two_way=True, pending_verification=False, contact_last_modified=datetime.utcnow() ) phone_number.save() event = MessagingEvent.objects.create( domain=self.domain_name, date=datetime.utcnow(), source=MessagingEvent.SOURCE_REMINDER, content_type=MessagingEvent.CONTENT_SMS, status=MessagingEvent.STATUS_COMPLETED ) MessagingSubEvent.objects.create( parent=event, date=datetime.utcnow(), recipient_type=MessagingEvent.RECIPIENT_CASE, content_type=MessagingEvent.CONTENT_SMS, status=MessagingEvent.STATUS_COMPLETED ) self._dump_and_load(expected_object_counts)
def test_backend(self): backend1 = SQLTestSMSBackend.objects.create( hq_api_id=SQLTestSMSBackend.get_api_id(), is_global=False, domain=self.domain, name='BACKEND1' ) backend2 = SQLTestSMSBackend.objects.create( hq_api_id=SQLTestSMSBackend.get_api_id(), is_global=False, domain=self.domain, name='BACKEND2' ) SQLMobileBackendMapping.set_default_domain_backend(self.domain, backend1) number = PhoneNumber(domain=self.domain, phone_number='+999123') self.assertEqual(number.backend, backend1) number.backend_id = backend2.name self.assertEqual(number.backend, backend2) number.backend_id = ' ' self.assertEqual(number.backend, backend1)
def test_by_owner_id(self): number = PhoneNumber.objects.create( domain=self.domain, owner_doc_type='X', owner_id='owner1', phone_number='999123', verified=True, pending_verification=False, is_two_way=True ) [lookup] = PhoneNumber.by_owner_id('owner1') self.assertEqual(lookup, number) # test cache clear number.owner_id = 'owner2' number.save() self.assertEqual(PhoneNumber.by_owner_id('owner1'), []) [lookup] = PhoneNumber.by_owner_id('owner2') self.assertEqual(lookup, number) number.verified = False number.is_two_way = False number.save() [lookup] = PhoneNumber.by_owner_id('owner2') self.assertFalse(lookup.verified) self.assertFalse(lookup.is_two_way)
def testCouchSyncToSQL(self): self.assertEqual(self.getCouchCount(), 0) self.assertEqual(self.getSQLCount(), 0) # Test Create couch_obj = VerifiedNumber() self.setRandomCouchObjectValues(couch_obj) couch_obj.save() sleep(1) self.assertEqual(self.getCouchCount(), 1) self.assertEqual(self.getSQLCount(), 1) sql_obj = PhoneNumber.objects.get(couch_id=couch_obj._id) self.checkFieldValues(couch_obj, sql_obj, PhoneNumber._migration_get_fields()) self.assertTrue(VerifiedNumber.get_db().get_rev(couch_obj._id).startswith('1-')) # Test Update self.setRandomCouchObjectValues(couch_obj) couch_obj.save() sleep(1) self.assertEqual(self.getCouchCount(), 1) self.assertEqual(self.getSQLCount(), 1) sql_obj = PhoneNumber.objects.get(couch_id=couch_obj._id) self.checkFieldValues(couch_obj, sql_obj, PhoneNumber._migration_get_fields()) self.assertTrue(VerifiedNumber.get_db().get_rev(couch_obj._id).startswith('2-')) # Test Delete couch_id = couch_obj._id couch_obj.delete() with self.assertRaises(ResourceNotFound): VerifiedNumber.get(couch_id) self.assertEqual(self.getCouchCount(), 0) self.assertEqual(self.getSQLCount(), 0)
def assertMatch(self, match, phone_search, suffix_search, owner_id_search): lookedup = PhoneNumber.get_two_way_number(phone_search) self.assertPhoneNumbersEqual(match, lookedup) lookedup = PhoneNumber.get_two_way_number_by_suffix(suffix_search) self.assertPhoneNumbersEqual(match, lookedup) [lookedup] = PhoneNumber.by_owner_id(owner_id_search) self.assertPhoneNumbersEqual(match, lookedup)
def process_incoming(msg, delay=True): v = VerifiedNumber.by_phone(msg.phone_number, include_pending=True) if v is not None and v.verified: msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.domain = v.domain contact = v.owner if isinstance(contact, CommCareUser) and hasattr(contact, 'location_id'): msg.location_id = contact.location_id msg.save() if msg.domain_scope: # only process messages for phones known to be associated with this domain if v is None or v.domain != msg.domain_scope: raise DomainScopeValidationError( 'Attempted to simulate incoming sms from phone number not ' \ 'verified with this domain' ) can_receive_sms = PhoneNumber.can_receive_sms(msg.phone_number) opt_in_keywords, opt_out_keywords = get_opt_keywords(msg) if is_opt_message(msg.text, opt_out_keywords) and can_receive_sms: if PhoneNumber.opt_out_sms(msg.phone_number): metadata = MessageMetadata(ignore_opt_out=True) text = get_message(MSG_OPTED_OUT, v, context=(opt_in_keywords[0],)) if v: send_sms_to_verified_number(v, text, metadata=metadata) else: send_sms(msg.domain, None, msg.phone_number, text, metadata=metadata) elif is_opt_message(msg.text, opt_in_keywords) and not can_receive_sms: if PhoneNumber.opt_in_sms(msg.phone_number): text = get_message(MSG_OPTED_IN, v, context=(opt_out_keywords[0],)) if v: send_sms_to_verified_number(v, text) else: send_sms(msg.domain, None, msg.phone_number, text) elif v is not None and v.verified: if domain_has_privilege(msg.domain, privileges.INBOUND_SMS): for h in settings.SMS_HANDLERS: try: handler = to_function(h) except: notify_exception(None, message=('error loading sms handler: %s' % h)) continue try: was_handled = handler(v, msg.text, msg=msg) except Exception, e: log_sms_exception(msg) was_handled = False if was_handled: break
def testGetOrCreate(self): before = self.mobile_worker1.get_or_create_phone_entry('999123') self.assertEqual(before.owner_doc_type, 'CommCareUser') self.assertEqual(before.owner_id, self.mobile_worker1.get_id) self.assertEqual(before.phone_number, '999123') self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 1) after = self.mobile_worker1.get_or_create_phone_entry('999123') self.assertEqual(before.pk, after.pk) self.assertEqual(after.owner_doc_type, 'CommCareUser') self.assertEqual(after.owner_id, self.mobile_worker1.get_id) self.assertEqual(after.phone_number, '999123') self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 1)
def testRetire(self): self.mobile_worker1.phone_numbers = ['9990001'] self.mobile_worker1.save() self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 1) self.assertPhoneEntries(self.mobile_worker1, ['9990001']) self.mobile_worker2.phone_numbers = ['9990002'] self.mobile_worker2.save() self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 2) self.assertPhoneEntries(self.mobile_worker2, ['9990002']) self.mobile_worker1.retire() self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 1) self.assertPhoneEntries(self.mobile_worker2, ['9990002'])
def get_inbound_phone_entry(msg): if msg.backend_id: backend = SQLMobileBackend.load(msg.backend_id, is_couch_id=True) if not backend.is_global and toggles.INBOUND_SMS_LENIENCY.enabled(backend.domain): p = PhoneNumber.get_two_way_number_with_domain_scope(msg.phone_number, backend.domains_with_access) return ( p, p is not None ) return ( PhoneNumber.get_reserved_number(msg.phone_number), False )
def save_verified_number(self, domain, phone_number, verified, backend_id=None, ivr_backend_id=None, only_one_number_allowed=False): """ Saves the given phone number as this contact's verified phone number. backend_id - the name of an SMSBackend to use when sending SMS to this number; if specified, this will override any project or global settings for which backend will be used to send sms to this number return The PhoneNumber raises InvalidFormatException if the phone number format is invalid raises PhoneNumberInUseException if the phone number is already in use by another contact """ from corehq.apps.sms.models import PhoneNumber phone_number = apply_leniency(phone_number) self.verify_unique_number(phone_number) if only_one_number_allowed: v = self.get_verified_number() else: v = self.get_verified_number(phone_number) if v is None: v = PhoneNumber( owner_doc_type=self.doc_type, owner_id=self.get_id ) v.domain = domain v.phone_number = phone_number v.verified = verified v.backend_id = backend_id v.ivr_backend_id = ivr_backend_id v.save()
def testDelete(self): self.mobile_worker1.get_or_create_phone_entry('999123') self.mobile_worker1.get_or_create_phone_entry('999124') self.mobile_worker1.get_or_create_phone_entry('999125') self.mobile_worker2.get_or_create_phone_entry('999126') self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 4) self.mobile_worker1.delete_phone_entry('999124') self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 3) entries = self.mobile_worker1.get_phone_entries() self.assertEqual(set(entries.keys()), set(['999123', '999125'])) entries = self.mobile_worker2.get_phone_entries() self.assertEqual(set(entries.keys()), set(['999126']))
def initiate_sms_verification_workflow(contact, phone_number): # For now this is only applicable to mobile workers assert isinstance(contact, CommCareUser) logged_event = MessagingEvent.get_current_verification_event( contact.domain, contact.get_id, phone_number) with CriticalSection(['verifying-phone-number-%s' % phone_number]): vn = PhoneNumber.by_phone(phone_number, include_pending=True) if vn: if vn.owner_id != contact.get_id: return VERIFICATION__ALREADY_IN_USE if vn.verified: return VERIFICATION__ALREADY_VERIFIED else: result = VERIFICATION__RESENT_PENDING else: contact.save_verified_number(contact.domain, phone_number, False) result = VERIFICATION__WORKFLOW_STARTED # Always create a new event when the workflow starts if logged_event: logged_event.status = MessagingEvent.STATUS_NOT_COMPLETED logged_event.save() logged_event = MessagingEvent.create_verification_event(contact.domain, contact) if not logged_event: logged_event = MessagingEvent.create_verification_event(contact.domain, contact) send_verification(contact.domain, contact, phone_number, logged_event) return result
def ivr_in(request): """ Handles tropo call requests """ if request.method == "POST": data = json.loads(request.body) phone_number = data["session"]["from"]["id"] # TODO: Implement tropo as an ivr backend. In the meantime, just log the call. if phone_number: cleaned_number = strip_plus(phone_number) v = PhoneNumber.by_extensive_search(cleaned_number) else: v = None # Save the call entry msg = Call( phone_number=cleaned_number, direction=INCOMING, date=datetime.utcnow(), backend_api=SQLTropoBackend.get_api_id(), ) if v is not None: msg.domain = v.domain msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.save() t = Tropo() t.reject() return HttpResponse(t.RenderJson()) else: return HttpResponseBadRequest("Bad Request")
def get_two_way_entry_or_phone_number(cls, recipient, try_usercase=True, domain_for_toggles=None): """ If recipient has a two-way number, returns it as a PhoneNumber entry. If recipient does not have a two-way number but has a phone number configured, returns the one-way phone number as a string. If try_usercase is True and recipient is a CommCareUser who doesn't have a two-way or one-way phone number, then it will try to get the two-way or one-way number from the user's user case if one exists. """ if settings.USE_PHONE_ENTRIES: phone_entry = get_two_way_number_for_recipient(recipient) if phone_entry: return phone_entry phone_number = get_one_way_number_for_recipient(recipient) if toggles.INBOUND_SMS_LENIENCY.enabled(domain_for_toggles) and \ toggles.ONE_PHONE_NUMBER_MULTIPLE_CONTACTS.enabled(domain_for_toggles): phone_entry = PhoneNumber.get_phone_number_for_owner(recipient.get_id, phone_number) if phone_entry: return phone_entry # Avoid processing phone numbers that are obviously fake (len <= 3) to # save on processing time if phone_number and len(phone_number) > 3: return phone_number if try_usercase and isinstance(recipient, CommCareUser) and recipient.memoized_usercase: return cls.get_two_way_entry_or_phone_number(recipient.memoized_usercase, domain_for_toggles=domain_for_toggles) return None
def test_opt_out_and_opt_in(self): self.assertEqual(PhoneBlacklist.objects.count(), 0) incoming('99912345678', 'join opt-test', 'GVI') v = PhoneNumber.get_two_way_number('99912345678') self.assertIsNotNone(v) incoming('99912345678', 'stop', 'GVI') self.assertEqual(PhoneBlacklist.objects.count(), 1) phone_number = PhoneBlacklist.objects.get(phone_number='99912345678') self.assertFalse(phone_number.send_sms) self.assertEqual(phone_number.domain, self.domain) self.assertIsNotNone(phone_number.last_sms_opt_out_timestamp) self.assertIsNone(phone_number.last_sms_opt_in_timestamp) sms = self.get_last_sms('+99912345678') self.assertEqual(sms.direction, 'O') self.assertEqual(sms.text, get_message(MSG_OPTED_OUT, context=('START',))) incoming('99912345678', 'start', 'GVI') self.assertEqual(PhoneBlacklist.objects.count(), 1) phone_number = PhoneBlacklist.objects.get(phone_number='99912345678') self.assertTrue(phone_number.send_sms) self.assertEqual(phone_number.domain, self.domain) self.assertIsNotNone(phone_number.last_sms_opt_out_timestamp) self.assertIsNotNone(phone_number.last_sms_opt_in_timestamp) sms = self.get_last_sms('+99912345678') self.assertEqual(sms.direction, 'O') self.assertEqual(sms.text, get_message(MSG_OPTED_IN, context=('STOP',)))
def testUserSyncNoChange(self): before = self.mobile_worker1.get_or_create_phone_entry('999123') before.set_two_way() before.set_verified() before.save() self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 1) self.mobile_worker1.phone_numbers = ['999123'] self.mobile_worker1.save() self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 1) after = self.mobile_worker1.get_phone_entries()['999123'] self.assertEqual(before.pk, after.pk) self.assertTrue(after.is_two_way) self.assertTrue(after.verified) self.assertFalse(after.pending_verification)
def test_sending_to_opted_out_number(self): self.assertEqual(PhoneBlacklist.objects.count(), 0) incoming('99912345678', 'join opt-test', 'GVI') v = PhoneNumber.get_two_way_number('99912345678') self.assertIsNotNone(v) send_sms_to_verified_number(v, 'hello') sms = self.get_last_sms('+99912345678') self.assertEqual(sms.direction, 'O') self.assertEqual(sms.text, 'hello') incoming('99912345678', 'stop', 'GVI') self.assertEqual(PhoneBlacklist.objects.count(), 1) phone_number = PhoneBlacklist.objects.get(phone_number='99912345678') self.assertFalse(phone_number.send_sms) send_sms_to_verified_number(v, 'hello') sms = self.get_last_sms('+99912345678') self.assertEqual(sms.direction, 'O') self.assertEqual(sms.text, 'hello') self.assertTrue(sms.error) self.assertEqual(sms.system_error_message, SMS.ERROR_PHONE_NUMBER_OPTED_OUT) incoming('99912345678', 'start', 'GVI') self.assertEqual(PhoneBlacklist.objects.count(), 1) phone_number = PhoneBlacklist.objects.get(phone_number='99912345678') self.assertTrue(phone_number.send_sms) send_sms_to_verified_number(v, 'hello') sms = self.get_last_sms('+99912345678') self.assertEqual(sms.direction, 'O') self.assertEqual(sms.text, 'hello') self.assertFalse(sms.error) self.assertIsNone(sms.system_error_message)
def handle_due_survey_action(domain, contact_id, session_id): with critical_section_for_smsforms_sessions(contact_id): session = SQLXFormsSession.by_session_id(session_id) if (not session or not session.session_is_open or session.current_action_due > utcnow()): return if session.current_action_is_a_reminder: # Resend the current question in the open survey to the contact p = PhoneNumber.get_phone_number_for_owner(session.connection_id, session.phone_number) if p: metadata = MessageMetadata( workflow=session.workflow, xforms_session_couch_id=session._id, ) resp = current_question(session.session_id) send_sms_to_verified_number( p, resp.event.text_prompt, metadata, logged_subevent=session.related_subevent) session.move_to_next_action() session.save() else: # Close the session session.close() session.save()
def process_incoming(msg): v = PhoneNumber.by_phone(msg.phone_number, include_pending=True) if v: msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.domain = v.domain msg.location_id = get_location_id_by_verified_number(v) msg.save() elif msg.domain_scope: msg.domain = msg.domain_scope msg.save() can_receive_sms = PhoneBlacklist.can_receive_sms(msg.phone_number) opt_in_keywords, opt_out_keywords = get_opt_keywords(msg) domain = v.domain if v else None if is_opt_message(msg.text, opt_out_keywords) and can_receive_sms: if PhoneBlacklist.opt_out_sms(msg.phone_number, domain=domain): metadata = MessageMetadata(ignore_opt_out=True) text = get_message(MSG_OPTED_OUT, v, context=(opt_in_keywords[0],)) if v: send_sms_to_verified_number(v, text, metadata=metadata) else: send_sms(msg.domain, None, msg.phone_number, text, metadata=metadata) elif is_opt_message(msg.text, opt_in_keywords) and not can_receive_sms: if PhoneBlacklist.opt_in_sms(msg.phone_number, domain=domain): text = get_message(MSG_OPTED_IN, v, context=(opt_out_keywords[0],)) if v: send_sms_to_verified_number(v, text) else: send_sms(msg.domain, None, msg.phone_number, text) else: handled = False is_verified = v is not None and v.verified if msg.domain and domain_has_privilege(msg.domain, privileges.INBOUND_SMS): handled = load_and_call(settings.CUSTOM_SMS_HANDLERS, v, msg.text, msg) if not handled and is_verified and is_contact_active(v.domain, v.owner_doc_type, v.owner_id): handled = load_and_call(settings.SMS_HANDLERS, v, msg.text, msg) if not handled and not is_verified: handled = process_pre_registration(msg) if not handled: handled = process_sms_registration(msg) if not handled: import verify verify.process_verification(v, msg) # If the sms queue is enabled, then the billable gets created in remove_from_queue() if ( not settings.SMS_QUEUE_ENABLED and msg.domain and domain_has_privilege(msg.domain, privileges.INBOUND_SMS) ): create_billable_for_sms(msg)
def test_case_owner(self): with create_test_case(self.domain, 'participant', 'test') as case: number = PhoneNumber(domain=self.domain, owner_doc_type='CommCareCase', owner_id=case.case_id) owner = number.owner self.assertTrue(is_commcarecase(owner)) self.assertEqual(owner.case_id, case.case_id)
def test_user_owner(self): mobile_user = CommCareUser.create(self.domain, 'abc', 'def') number = PhoneNumber(owner_doc_type='CommCareUser', owner_id=mobile_user.get_id) owner = number.owner self.assertTrue(isinstance(owner, CommCareUser)) self.assertEqual(owner.get_id, mobile_user.get_id) web_user = WebUser.create(self.domain, 'ghi', 'jkl') number = PhoneNumber(owner_doc_type='WebUser', owner_id=web_user.get_id) owner = number.owner self.assertTrue(isinstance(owner, WebUser)) self.assertEqual(owner.get_id, web_user.get_id) number = PhoneNumber(owner_doc_type='X') self.assertIsNone(number.owner)
def test_other_registration_from_invite(self): self.domain_obj.sms_mobile_worker_registration_enabled = True self.domain_obj.enable_registration_welcome_sms_for_mobile_worker = True self.domain_obj.save() user_data = {'abc': 'def'} # Initiate Registration Workflow SelfRegistrationInvitation.initiate_workflow( self.domain, [SelfRegistrationUserInfo('999123', user_data)], app_id=self.app_id, ) self.assertRegistrationInvitation( phone_number='999123', app_id=self.app_id, phone_type=None, android_only=False, require_email=False, custom_user_data=user_data, status=SelfRegistrationInvitation.STATUS_PENDING, ) self.assertLastOutgoingSMS( '+999123', [_MESSAGES[MSG_MOBILE_WORKER_INVITATION_START]]) # Choose phone type 'other' incoming('+999123', '2', self.backend.hq_api_id) self.assertRegistrationInvitation( phone_number='999123', app_id=self.app_id, phone_type=SelfRegistrationInvitation.PHONE_TYPE_OTHER, android_only=False, require_email=False, custom_user_data=user_data, status=SelfRegistrationInvitation.STATUS_PENDING, ) self.assertLastOutgoingSMS( '+999123', [_MESSAGES[MSG_MOBILE_WORKER_JAVA_INVITATION].format(self.domain)]) # Register over SMS incoming('+999123', 'JOIN {} WORKER test'.format(self.domain), self.backend.hq_api_id) user = CommCareUser.get_by_username( format_username('test', self.domain)) self.assertIsNotNone(user) self.assertEqual(user.user_data, user_data) self.assertEqual(PhoneNumber.by_phone('999123').owner_id, user.get_id) self.assertLastOutgoingSMS( '+999123', [_MESSAGES[MSG_REGISTRATION_WELCOME_MOBILE_WORKER]]) self.assertRegistrationInvitation( status=SelfRegistrationInvitation.STATUS_REGISTERED, )
def test_suffix_lookup(self): number1 = PhoneNumber.objects.create(domain=self.domain, owner_doc_type='X', owner_id='X', phone_number='999123', verified=True, pending_verification=False, is_two_way=True) number2 = PhoneNumber.objects.create(domain=self.domain, owner_doc_type='X', owner_id='X', phone_number='999223', verified=True, pending_verification=False, is_two_way=True) self.assertEqual(PhoneNumber.get_two_way_number_by_suffix('1 23'), number1) self.assertEqual(PhoneNumber.get_two_way_number_by_suffix('2 23'), number2) self.assertIsNone(PhoneNumber.get_two_way_number_by_suffix('23')) # test update number1.phone_number = '999124' number1.save() number2.phone_number = '999224' number2.save() self.assertIsNone(PhoneNumber.get_two_way_number_by_suffix('1 23')) self.assertIsNone(PhoneNumber.get_two_way_number_by_suffix('2 23')) self.assertEqual(PhoneNumber.get_two_way_number_by_suffix('124'), number1) self.assertEqual(PhoneNumber.get_two_way_number_by_suffix('224'), number2)
def test_suffix_lookup(self): number1 = PhoneNumber.objects.create( domain=self.domain, owner_doc_type='X', owner_id='X', phone_number='999123', verified=True, pending_verification=False, is_two_way=True ) number2 = PhoneNumber.objects.create( domain=self.domain, owner_doc_type='X', owner_id='X', phone_number='999223', verified=True, pending_verification=False, is_two_way=True ) self.assertEqual(PhoneNumber.get_two_way_number_by_suffix('1 23'), number1) self.assertEqual(PhoneNumber.get_two_way_number_by_suffix('2 23'), number2) self.assertIsNone(PhoneNumber.get_two_way_number_by_suffix('23')) # test update number1.phone_number = '999124' number1.save() number2.phone_number = '999224' number2.save() self.assertIsNone(PhoneNumber.get_two_way_number_by_suffix('1 23')) self.assertIsNone(PhoneNumber.get_two_way_number_by_suffix('2 23')) self.assertEqual(PhoneNumber.get_two_way_number_by_suffix('124'), number1) self.assertEqual(PhoneNumber.get_two_way_number_by_suffix('224'), number2)
def send_message_via_backend(msg, backend=None, orig_phone_number=None): """send sms using a specific backend msg - outbound message object backend - MobileBackend object to use for sending; if None, use msg.outbound_backend orig_phone_number - the originating phone number to use when sending; this is sent in if the backend supports load balancing """ try: msg.text = clean_text(msg.text) except Exception: logging.exception("Could not clean text for sms dated '%s' in domain '%s'" % (msg.date, msg.domain)) try: if not domain_has_privilege(msg.domain, privileges.OUTBOUND_SMS): raise Exception( ("Domain '%s' does not have permission to send SMS." " Please investigate why this function was called.") % msg.domain ) phone_obj = PhoneNumber.get_by_phone_number_or_none(msg.phone_number) if phone_obj and not phone_obj.send_sms: if msg.ignore_opt_out and phone_obj.can_opt_in: # If ignore_opt_out is True on the message, then we'll still # send it. However, if we're not letting the phone number # opt back in and it's in an opted-out state, we will not # send anything to it no matter the state of the ignore_opt_out # flag. pass else: msg.set_system_error(SMS.ERROR_PHONE_NUMBER_OPTED_OUT) return False if not backend: backend = msg.outbound_backend # note: this will handle "verified" contacts that are still pending # verification, thus the backend is None. it's best to only call # send_sms_to_verified_number on truly verified contacts, though if not msg.backend_id: msg.backend_id = backend._id if backend.domain_is_authorized(msg.domain): backend.send(msg, orig_phone_number=orig_phone_number) else: raise BackendAuthorizationException("Domain '%s' is not authorized to use backend '%s'" % (msg.domain, backend._id)) try: msg.backend_api = backend.__class__.get_api_id() except Exception: pass msg.save() create_billable_for_sms(msg) return True except Exception: log_sms_exception(msg) return False
def process_incoming(msg): v = PhoneNumber.by_phone(msg.phone_number, include_pending=True) if v is not None and v.verified: msg.couch_recipient_doc_type = v.owner_doc_type msg.couch_recipient = v.owner_id msg.domain = v.domain msg.location_id = get_location_id_by_verified_number(v) msg.save() if msg.domain_scope: # only process messages for phones known to be associated with this domain if v is None or v.domain != msg.domain_scope: raise DomainScopeValidationError( 'Attempted to simulate incoming sms from phone number not ' \ 'verified with this domain' ) can_receive_sms = PhoneBlacklist.can_receive_sms(msg.phone_number) opt_in_keywords, opt_out_keywords = get_opt_keywords(msg) domain = v.domain if v else None if is_opt_message(msg.text, opt_out_keywords) and can_receive_sms: if PhoneBlacklist.opt_out_sms(msg.phone_number, domain=domain): metadata = MessageMetadata(ignore_opt_out=True) text = get_message(MSG_OPTED_OUT, v, context=(opt_in_keywords[0],)) if v: send_sms_to_verified_number(v, text, metadata=metadata) else: send_sms(msg.domain, None, msg.phone_number, text, metadata=metadata) elif is_opt_message(msg.text, opt_in_keywords) and not can_receive_sms: if PhoneBlacklist.opt_in_sms(msg.phone_number, domain=domain): text = get_message(MSG_OPTED_IN, v, context=(opt_out_keywords[0],)) if v: send_sms_to_verified_number(v, text) else: send_sms(msg.domain, None, msg.phone_number, text) elif v is not None and v.verified: if ( domain_has_privilege(msg.domain, privileges.INBOUND_SMS) and is_contact_active(v.domain, v.owner_doc_type, v.owner_id) ): for h in settings.SMS_HANDLERS: try: handler = to_function(h) except: notify_exception(None, message=('error loading sms handler: %s' % h)) continue try: was_handled = handler(v, msg.text, msg=msg) except Exception, e: log_sms_exception(msg) was_handled = False if was_handled: break
def test_by_domain(self): number1 = PhoneNumber.objects.create( domain=self.domain, owner_doc_type='X', owner_id='X', phone_number='999123', verified=True, pending_verification=False, is_two_way=True ) number2 = PhoneNumber.objects.create( domain=self.domain, owner_doc_type='X', owner_id='X', phone_number='999124', verified=False, pending_verification=False, is_two_way=False ) number3 = PhoneNumber.objects.create( domain=self.domain + 'X', owner_doc_type='X', owner_id='X', phone_number='999124', verified=True, pending_verification=False, is_two_way=True ) self.addCleanup(number3.delete) self.assertEqual( set(PhoneNumber.by_domain(self.domain)), set([number1, number2]) ) self.assertEqual( set(PhoneNumber.by_domain(self.domain, ids_only=True)), set([number1.couch_id, number2.couch_id]) ) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)
def get_inbound_phone_entry(phone_number, backend_id=None): if backend_id: backend = SQLMobileBackend.load(backend_id, is_couch_id=True) if toggles.INBOUND_SMS_LENIENCY.enabled(backend.domain): p = None if toggles.ONE_PHONE_NUMBER_MULTIPLE_CONTACTS.enabled(backend.domain): running_session_info = XFormsSessionSynchronization.get_running_session_info_for_channel( SMSChannel(backend_id=backend_id, phone_number=phone_number) ) contact_id = running_session_info.contact_id if contact_id: p = PhoneNumber.get_phone_number_for_owner(contact_id, phone_number) if p is not None: return ( p, True ) elif running_session_info.session_id: # This would be very unusual, as it would mean the supposedly running form session # is linked to a phone number, contact pair that doesn't exist in the PhoneNumber table notify_error( "Contact from running session has no match in PhoneNumber table. " "Only known way for this to happen is if you " "unregister a phone number for a contact " "while they are in an active session.", details={ 'running_session_info': running_session_info } ) # NOTE: I don't think the backend could ever be global here since global backends # don't have a 'domain' and so the toggles would never be activated if not backend.is_global: p = PhoneNumber.get_two_way_number_with_domain_scope(phone_number, backend.domains_with_access) return ( p, p is not None ) return ( PhoneNumber.get_reserved_number(phone_number), False )
def test_android_only_registration_from_invite(self): self.domain_obj.sms_mobile_worker_registration_enabled = True self.domain_obj.enable_registration_welcome_sms_for_mobile_worker = True self.domain_obj.save() # Initiate Registration Workflow with patch('corehq.apps.sms.models.SelfRegistrationInvitation.odk_url') as mock_odk_url, \ patch.object(SelfRegistrationInvitation, 'get_user_registration_url', return_value=DUMMY_REGISTRATION_URL), \ patch.object(SelfRegistrationInvitation, 'get_app_info_url', return_value=DUMMY_APP_INFO_URL): mock_odk_url.__get__ = Mock(return_value=DUMMY_APP_ODK_URL) SelfRegistrationInvitation.initiate_workflow( self.domain, [SelfRegistrationUserInfo('999123')], app_id=self.app_id, android_only=True, ) self.assertRegistrationInvitation( phone_number='999123', app_id=self.app_id, phone_type=SelfRegistrationInvitation.PHONE_TYPE_ANDROID, android_only=True, require_email=False, custom_user_data={}, status=SelfRegistrationInvitation.STATUS_PENDING, ) self.assertLastOutgoingSMS('+999123', [ _MESSAGES[MSG_MOBILE_WORKER_ANDROID_INVITATION].format( DUMMY_REGISTRATION_URL), '[commcare app - do not delete] {}'.format(DUMMY_APP_INFO_URL_B64), ]) invite = self._get_sms_registration_invitation() c = Client() response = c.post( '/a/{}/settings/users/commcare/register/{}/'.format( self.domain, invite.token), { 'username': '******', 'password': '******', 'password2': 'abc', 'email': '*****@*****.**', }) self.assertEqual(response.status_code, 200) user = CommCareUser.get_by_username( format_username('new_user', self.domain)) self.assertIsNotNone(user) self.assertEqual(user.user_data, self.default_user_data) self.assertEqual(user.email, '*****@*****.**') self.assertEqual( PhoneNumber.get_two_way_number('999123').owner_id, user.get_id) self.assertRegistrationInvitation( status=SelfRegistrationInvitation.STATUS_REGISTERED, )
def _fmt_status(self, number): if number.verified: return "Verified" elif number.pending_verification: return "Verification Pending" elif ( not (number.is_two_way or number.pending_verification) and PhoneNumber.get_reserved_number(number.phone_number) ): return "Already In Use" return "Not Verified"
def _sync_case_phone_number(contact_case): phone_info = contact_case.get_phone_info() lock_keys = ['sync-case-phone-number-for-%s' % contact_case.case_id] if phone_info.phone_number: lock_keys.append('verifying-phone-number-%s' % phone_info.phone_number) with CriticalSection(lock_keys, timeout=5 * 60): phone_number = contact_case.get_verified_number() if ( phone_number and phone_number.contact_last_modified and phone_number.contact_last_modified >= contact_case.server_modified_on ): return if phone_info.requires_entry: try: contact_case.verify_unique_number(phone_info.phone_number) except (InvalidFormatException, PhoneNumberInUseException): if phone_number: phone_number.delete() return if not phone_number: phone_number = PhoneNumber( domain=contact_case.domain, owner_doc_type=contact_case.doc_type, owner_id=contact_case.case_id, ) elif _phone_number_is_same(phone_number, phone_info): return phone_number.phone_number = phone_info.phone_number phone_number.backend_id = phone_info.sms_backend_id phone_number.ivr_backend_id = phone_info.ivr_backend_id phone_number.verified = True phone_number.contact_last_modified = contact_case.server_modified_on phone_number.save() else: if phone_number: phone_number.delete()
def run_script(self, script): commands = self.parse_script(script) for command in commands: phone_number = command['phone_number'] v = PhoneNumber.by_phone(phone_number) if command['direction'] == '>': incoming(phone_number, command['text'], v.backend_id) else: msg = self.get_last_outbound_sms(v.owner_doc_type, v.owner_id) self.assertEqual(msg.text, unicode(command['text'])) self.assertEqual(strip_plus(msg.phone_number), strip_plus(phone_number)) msg.delete()
def test_other_registration_from_invite(self): self.domain_obj.sms_mobile_worker_registration_enabled = True self.domain_obj.enable_registration_welcome_sms_for_mobile_worker = True self.domain_obj.save() user_data = {'abc': 'def'} # Initiate Registration Workflow SelfRegistrationInvitation.initiate_workflow( self.domain, [SelfRegistrationUserInfo('999123', user_data)], app_id=self.app_id, ) self.assertRegistrationInvitation( phone_number='999123', app_id=self.app_id, phone_type=None, android_only=False, require_email=False, custom_user_data=user_data, status=SelfRegistrationInvitation.STATUS_PENDING, ) self.assertLastOutgoingSMS('+999123', [_MESSAGES[MSG_MOBILE_WORKER_INVITATION_START]]) # Choose phone type 'other' incoming('+999123', '2', self.backend.hq_api_id) self.assertRegistrationInvitation( phone_number='999123', app_id=self.app_id, phone_type=SelfRegistrationInvitation.PHONE_TYPE_OTHER, android_only=False, require_email=False, custom_user_data=user_data, status=SelfRegistrationInvitation.STATUS_PENDING, ) self.assertLastOutgoingSMS('+999123', [_MESSAGES[MSG_MOBILE_WORKER_JAVA_INVITATION].format(self.domain)]) # Register over SMS incoming('+999123', 'JOIN {} WORKER test'.format(self.domain), self.backend.hq_api_id) user = CommCareUser.get_by_username(format_username('test', self.domain)) self.assertIsNotNone(user) self.assertEqual(user.user_data, user_data) self.assertEqual(PhoneNumber.by_phone('999123').owner_id, user.get_id) self.assertLastOutgoingSMS('+999123', [_MESSAGES[MSG_REGISTRATION_WELCOME_MOBILE_WORKER]]) self.assertRegistrationInvitation( status=SelfRegistrationInvitation.STATUS_REGISTERED, )
def test_by_owner_id(self): number = PhoneNumber.objects.create(domain=self.domain, owner_doc_type='X', owner_id='owner1', phone_number='999123', verified=True) [lookup] = PhoneNumber.by_owner_id('owner1') self.assertEqual(lookup, number) # test cache clear number.owner_id = 'owner2' number.save() self.assertEqual(PhoneNumber.by_owner_id('owner1').count(), 0) [lookup] = PhoneNumber.by_owner_id('owner2') self.assertEqual(lookup, number) number.verified = False number.save() [lookup] = PhoneNumber.by_owner_id('owner2') self.assertFalse(lookup.verified)
def test_invalid_phone_format(self): extra_number = PhoneNumber.objects.create( domain=self.domain, owner_doc_type='X', owner_id='X', phone_number='999123', verified=True, pending_verification=False, is_two_way=True ) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1) with create_test_case(self.domain, 'participant', 'test1', drop_signals=False) as case: case = self.set_case_property(case, 'contact_phone_number', '99987658765') case = self.set_case_property(case, 'contact_phone_number_is_verified', '1') self.assertIsNotNone(self.get_case_phone_number(case)) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2) case = self.set_case_property(case, 'contact_phone_number', 'xyz') self.assertIsNone(self.get_case_phone_number(case)) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1)
def run_script(self, script): commands = self.parse_script(script) for command in commands: phone_number = command['phone_number'] v = PhoneNumber.get_two_way_number(phone_number) if command['direction'] == '>': incoming(phone_number, command['text'], v.backend_id) else: msg = self.get_last_outbound_sms(v.owner_doc_type, v.owner_id) self.assertEqual(msg.text, unicode(command['text'])) self.assertEqual(strip_plus(msg.phone_number), strip_plus(phone_number)) msg.delete()
def test_case_soft_delete(self): extra_number = PhoneNumber.objects.create( domain=self.domain, owner_doc_type='X', owner_id='X', phone_number='999123', verified=True, pending_verification=False, is_two_way=True ) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1) with create_test_case(self.domain, 'participant', 'test1', drop_signals=False) as case: case = self.set_case_property(case, 'contact_phone_number', '99987658765') case = self.set_case_property(case, 'contact_phone_number_is_verified', '1') self.assertIsNotNone(self.get_case_phone_number(case)) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2) tag_cases_as_deleted_and_remove_indices(self.domain, [case.case_id], '123', datetime.utcnow()) self.assertIsNone(self.get_case_phone_number(case)) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1)
def test_case_phone_number_updates(self): extra_number = PhoneNumber.objects.create( domain=self.domain, owner_doc_type='X', owner_id='X', phone_number='999123', verified=True, pending_verification=False, is_two_way=True ) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1) with create_test_case(self.domain, 'participant', 'test1', drop_signals=False) as case: self.assertIsNone(self.get_case_phone_number(case)) case = self.set_case_property(case, 'contact_phone_number', '99987658765') self.assertPhoneNumberDetails(case, '99987658765', None, None, False, False, False) pk = self.get_case_phone_number(case).pk self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2) case = self.set_case_property(case, 'contact_phone_number_is_verified', '1') self.assertPhoneNumberDetails(case, '99987658765', None, None, True, False, True, pk=pk) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2) case = self.set_case_property(case, 'contact_phone_number', '99987698769') self.assertPhoneNumberDetails(case, '99987698769', None, None, True, False, True) pk = self.get_case_phone_number(case).pk self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2) case = self.set_case_property(case, 'contact_backend_id', 'sms-backend') self.assertPhoneNumberDetails(case, '99987698769', 'sms-backend', None, True, False, True, pk=pk) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2) case = self.set_case_property(case, 'contact_ivr_backend_id', 'ivr-backend') self.assertPhoneNumberDetails(case, '99987698769', 'sms-backend', 'ivr-backend', True, False, True, pk=pk) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2) # If phone entry is ahead of the case in terms of contact_last_modified, no update should happen v = self.get_case_phone_number(case) v.contact_last_modified += timedelta(days=1) v.save() with patch('corehq.apps.sms.models.PhoneNumber.save') as mock_save: case = self.set_case_property(case, 'contact_phone_number', '99912341234') self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2) mock_save.assert_not_called() self.assertEqual(PhoneNumber.get_two_way_number('999123'), extra_number)
def verify_unique_number(self, phone_number): """ Verifies that the given phone number is not already in use by any other contacts. return void raises InvalidFormatException if the phone number format is invalid raises PhoneNumberInUseException if the phone number is already in use by another contact """ from corehq.apps.sms.models import PhoneNumber self.validate_number_format(phone_number) v = PhoneNumber.by_phone(phone_number, include_pending=True) if v is not None and (v.owner_doc_type != self.doc_type or v.owner_id != self.get_id): raise PhoneNumberInUseException("Phone number is already in use.")
def test_delete_phone_numbers_for_owners(self): with self.create_case_contact('9990001') as case1, \ self.create_case_contact('9990002') as case2, \ self.create_case_contact('9990003') as case3: self.assertEqual(len(PhoneNumber.by_owner_id(case1.case_id)), 1) self.assertEqual(len(PhoneNumber.by_owner_id(case2.case_id)), 1) self.assertEqual(len(PhoneNumber.by_owner_id(case3.case_id)), 1) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 3) delete_phone_numbers_for_owners([case2.case_id, case3.case_id]) self.assertEqual(len(PhoneNumber.by_owner_id(case1.case_id)), 1) self.assertEqual(len(PhoneNumber.by_owner_id(case2.case_id)), 0) self.assertEqual(len(PhoneNumber.by_owner_id(case3.case_id)), 0) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1)
def test_android_only_registration_from_invite(self): self.domain_obj.sms_mobile_worker_registration_enabled = True self.domain_obj.enable_registration_welcome_sms_for_mobile_worker = True self.domain_obj.save() # Initiate Registration Workflow with patch('corehq.apps.sms.models.SelfRegistrationInvitation.odk_url') as mock_odk_url, \ patch.object(SelfRegistrationInvitation, 'get_user_registration_url', return_value=DUMMY_REGISTRATION_URL), \ patch.object(SelfRegistrationInvitation, 'get_app_info_url', return_value=DUMMY_APP_INFO_URL): mock_odk_url.__get__ = Mock(return_value=DUMMY_APP_ODK_URL) SelfRegistrationInvitation.initiate_workflow( self.domain, [SelfRegistrationUserInfo('999123')], app_id=self.app_id, android_only=True, ) self.assertRegistrationInvitation( phone_number='999123', app_id=self.app_id, phone_type=SelfRegistrationInvitation.PHONE_TYPE_ANDROID, android_only=True, require_email=False, custom_user_data={}, status=SelfRegistrationInvitation.STATUS_PENDING, ) self.assertLastOutgoingSMS('+999123', [ _MESSAGES[MSG_MOBILE_WORKER_ANDROID_INVITATION].format(DUMMY_REGISTRATION_URL), '[commcare app - do not delete] {}'.format(DUMMY_APP_INFO_URL_B64), ]) invite = self._get_sms_registration_invitation() c = Client() response = c.post('/a/{}/settings/users/commcare/register/{}/'.format(self.domain, invite.token), { 'username': '******', 'password': '******', 'password2': 'abc', 'email': '*****@*****.**', }) self.assertEqual(response.status_code, 200) user = CommCareUser.get_by_username(format_username('new_user', self.domain)) self.assertIsNotNone(user) self.assertEqual(user.user_data, {}) self.assertEqual(user.email, '*****@*****.**') self.assertEqual(PhoneNumber.by_phone('999123').owner_id, user.get_id) self.assertRegistrationInvitation( status=SelfRegistrationInvitation.STATUS_REGISTERED, )
def test_invalid_phone_format(self): extra_number = PhoneNumber.objects.create( domain=self.domain, owner_doc_type='X', owner_id='X', phone_number='999123', verified=True, pending_verification=False, is_two_way=True ) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1) with create_test_case(self.domain, 'participant', 'test1', drop_signals=False) as case: case = self.set_case_property(case, 'contact_phone_number', '99987658765') case = self.set_case_property(case, 'contact_phone_number_is_verified', '1') self.assertIsNotNone(self.get_case_phone_number(case)) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2) case = self.set_case_property(case, 'contact_phone_number', 'xyz') self.assertIsNone(self.get_case_phone_number(case)) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1) self.assertEqual(PhoneNumber.get_two_way_number('999123'), extra_number)
def test_phone_number_already_in_use(self): self.assertEqual(PhoneNumber.count_by_domain(self.domain), 0) with create_test_case(self.domain, 'participant', 'test1', drop_signals=False) as case1, \ create_test_case(self.domain, 'participant', 'test2', drop_signals=False) as case2: case1 = self.set_case_property(case1, 'contact_phone_number', '99987658765') case1 = self.set_case_property(case1, 'contact_phone_number_is_verified', '1') case2 = self.set_case_property(case2, 'contact_phone_number', '99987698769') case2 = self.set_case_property(case2, 'contact_phone_number_is_verified', '1') self.assertIsNotNone(self.get_case_phone_number(case1)) self.assertIsNotNone(self.get_case_phone_number(case2)) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2) case2 = self.set_case_property(case2, 'contact_phone_number', '99987658765') self.assertIsNotNone(self.get_case_phone_number(case1)) self.assertIsNotNone(self.get_case_phone_number(case2)) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2) self.assertPhoneNumberDetails(case1, '99987658765', None, None, True, False, True) self.assertPhoneNumberDetails(case2, '99987658765', None, None, False, False, False)
def handle_due_survey_action(domain, contact_id, session_id): with critical_section_for_smsforms_sessions(contact_id): session = SQLXFormsSession.by_session_id(session_id) if (not session or not session.session_is_open or session.current_action_due > utcnow()): return if toggles.ONE_PHONE_NUMBER_MULTIPLE_CONTACTS.enabled(domain): if not XFormsSessionSynchronization.claim_channel_for_session( session): from .management.commands import handle_survey_actions # Unless we release this lock, handle_survey_actions will be unable to requeue this task # for the default duration of 1h, which we don't want handle_survey_actions.Command.get_enqueue_lock( session_id, session.current_action_due).release() return if session_is_stale(session): # If a session is having some unrecoverable errors that aren't benefitting from # being retried, those errors should show up in sentry log and the fix should # be dealt with. In terms of the current session itself, we just close it out # to allow new sessions to start. session.mark_completed(False) session.save() return if session.current_action_is_a_reminder: # Resend the current question in the open survey to the contact p = PhoneNumber.get_phone_number_for_owner(session.connection_id, session.phone_number) if p: metadata = MessageMetadata( workflow=session.workflow, xforms_session_couch_id=session._id, ) resp = FormplayerInterface(session.session_id, domain).current_question() send_sms_to_verified_number( p, resp.event.text_prompt, metadata, logged_subevent=session.related_subevent) session.move_to_next_action() session.save() else: # Close the session session.close() session.save()
def test_backend(self): backend1 = SQLTestSMSBackend.objects.create( hq_api_id=SQLTestSMSBackend.get_api_id(), is_global=False, domain=self.domain, name='BACKEND1') backend2 = SQLTestSMSBackend.objects.create( hq_api_id=SQLTestSMSBackend.get_api_id(), is_global=False, domain=self.domain, name='BACKEND2') SQLMobileBackendMapping.set_default_domain_backend( self.domain, backend1) number = PhoneNumber(domain=self.domain, phone_number='+999123') self.assertEqual(number.backend, backend1) number.backend_id = backend2.name self.assertEqual(number.backend, backend2) number.backend_id = ' ' self.assertEqual(number.backend, backend1)
def testGetPhoneEntries(self): number1 = self.mobile_worker1.get_or_create_phone_entry('999123') number2 = self.mobile_worker1.get_or_create_phone_entry('999124') self.mobile_worker1.get_or_create_phone_entry('999125') number4 = self.mobile_worker2.get_or_create_phone_entry('999126') number1.set_two_way() number2.set_pending_verification() number4.set_two_way() self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 4) entries = self.mobile_worker1.get_phone_entries() self.assertEqual(set(entries.keys()), set(['999123', '999124', '999125'])) entries = self.mobile_worker1.get_two_way_numbers() self.assertEqual(set(entries.keys()), set(['999123']))
def test_filter_pending(self): v1 = PhoneNumber(verified=True) v2 = PhoneNumber(verified=False) self.assertIsNone( PhoneNumber._filter_pending(None, include_pending=True)) self.assertIsNone( PhoneNumber._filter_pending(None, include_pending=False)) self.assertEqual( v1, PhoneNumber._filter_pending(v1, include_pending=False)) self.assertIsNone( PhoneNumber._filter_pending(v2, include_pending=False)) self.assertEqual(v1, PhoneNumber._filter_pending(v1, include_pending=True)) self.assertEqual(v2, PhoneNumber._filter_pending(v2, include_pending=True))
def log_call(phone_number, gateway_session_id, backend=None): cleaned_number = strip_plus(phone_number) v = PhoneNumber.by_extensive_search(cleaned_number) call = Call( phone_number=cleaned_number, direction=INCOMING, date=datetime.utcnow(), backend_api=backend.get_api_id() if backend else None, backend_id=backend.couch_id if backend else None, gateway_session_id=gateway_session_id, ) if v: call.domain = v.domain call.couch_recipient_doc_type = v.owner_doc_type call.couch_recipient = v.owner_id call.save()
def test_case_phone_number_updates(self): extra_number = PhoneNumber.objects.create( domain=self.domain, owner_doc_type='X', owner_id='X', phone_number='999123', verified=True, pending_verification=False, is_two_way=True ) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1) with create_test_case(self.domain, 'participant', 'test1', drop_signals=False) as case: self.assertIsNone(self.get_case_phone_number(case)) case = self.set_case_property(case, 'contact_phone_number', '99987658765') self.assertPhoneNumberDetails(case, '99987658765', None, None, False, False, False) pk = self.get_case_phone_number(case).pk self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2) case = self.set_case_property(case, 'contact_phone_number_is_verified', '1') self.assertPhoneNumberDetails(case, '99987658765', None, None, True, False, True, pk=pk) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2) case = self.set_case_property(case, 'contact_phone_number', '99987698769') self.assertPhoneNumberDetails(case, '99987698769', None, None, True, False, True) pk = self.get_case_phone_number(case).pk self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2) case = self.set_case_property(case, 'contact_backend_id', 'sms-backend') self.assertPhoneNumberDetails(case, '99987698769', 'sms-backend', None, True, False, True, pk=pk) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2) case = self.set_case_property(case, 'contact_ivr_backend_id', 'ivr-backend') self.assertPhoneNumberDetails(case, '99987698769', 'sms-backend', 'ivr-backend', True, False, True, pk=pk) self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2) # If phone entry is ahead of the case in terms of contact_last_modified, no update should happen v = self.get_case_phone_number(case) v.contact_last_modified += timedelta(days=1) v.save() with patch('corehq.apps.sms.models.PhoneNumber.save') as mock_save: case = self.set_case_property(case, 'contact_phone_number', '99912341234') self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2) mock_save.assert_not_called()
def test_extensive_search(self): number = PhoneNumber.objects.create(domain=self.domain, owner_doc_type='X', owner_id='X', phone_number='999123', verified=True) self.assertEqual(PhoneNumber.by_extensive_search('999123'), number) self.assertEqual(PhoneNumber.by_extensive_search('0999123'), number) self.assertEqual(PhoneNumber.by_extensive_search('00999123'), number) self.assertEqual(PhoneNumber.by_extensive_search('000999123'), number) self.assertEqual(PhoneNumber.by_extensive_search('123'), number) self.assertIsNone(PhoneNumber.by_extensive_search('999124'), number)
def save_verified_number(self, domain, phone_number, verified, backend_id=None, ivr_backend_id=None, only_one_number_allowed=False): """ Saves the given phone number as this contact's verified phone number. backend_id - the name of an SMSBackend to use when sending SMS to this number; if specified, this will override any project or global settings for which backend will be used to send sms to this number return The PhoneNumber raises InvalidFormatException if the phone number format is invalid raises PhoneNumberInUseException if the phone number is already in use by another contact """ from corehq.apps.sms.models import PhoneNumber phone_number = apply_leniency(phone_number) self.verify_unique_number(phone_number) if only_one_number_allowed: v = self.get_verified_number() else: v = self.get_verified_number(phone_number) if v is None: v = PhoneNumber(owner_doc_type=self.doc_type, owner_id=self.get_id) v.domain = domain v.phone_number = phone_number v.verified = verified v.backend_id = backend_id v.ivr_backend_id = ivr_backend_id v.save() return v
def handle_due_survey_action(domain, contact_id, session_id): with critical_section_for_smsforms_sessions(contact_id): session = SQLXFormsSession.by_session_id(session_id) if ( not session or not session.session_is_open or session.current_action_due > utcnow() ): return if session_is_stale(session): # If a session is having some unrecoverable errors that aren't benefitting from # being retried, those errors should show up in sentry log and the fix should # be dealt with. In terms of the current session itself, we just close it out # to allow new sessions to start. session.mark_completed(False) session.save() return if session.current_action_is_a_reminder: # Resend the current question in the open survey to the contact p = PhoneNumber.get_phone_number_for_owner(session.connection_id, session.phone_number) if p: metadata = MessageMetadata( workflow=session.workflow, xforms_session_couch_id=session._id, ) resp = current_question(session.session_id, domain) send_sms_to_verified_number( p, resp.event.text_prompt, metadata, logged_subevent=session.related_subevent ) session.move_to_next_action() session.save() else: # Close the session session.close() session.save()
def initiate_sms_verification_workflow(contact, phone_number): # For now this is only applicable to mobile workers assert isinstance(contact, CommCareUser) phone_number = apply_leniency(phone_number) logged_event = MessagingEvent.get_current_verification_event( contact.domain, contact.get_id, phone_number) p = PhoneNumber.get_reserved_number(phone_number) if p: if p.owner_id != contact.get_id: return VERIFICATION__ALREADY_IN_USE if p.verified: return VERIFICATION__ALREADY_VERIFIED else: result = VERIFICATION__RESENT_PENDING else: entry = contact.get_or_create_phone_entry(phone_number) try: entry.set_pending_verification() except PhoneNumberInUseException: # On the off chance that the phone number was reserved between # the check above and now return VERIFICATION__ALREADY_IN_USE result = VERIFICATION__WORKFLOW_STARTED # Always create a new event when the workflow starts if logged_event: logged_event.status = MessagingEvent.STATUS_NOT_COMPLETED logged_event.save() logged_event = MessagingEvent.create_verification_event( contact.domain, contact) if not logged_event: logged_event = MessagingEvent.create_verification_event( contact.domain, contact) send_verification(contact.domain, contact, phone_number, logged_event) return result
def get_channel_for_contact(contact_id, phone_number): """ For a given contact_id, phone_number pair, look up the gateway to be used and return the result as a Channel If a PhoneNumber object does not exist for the pair, or attempting to determine the gateway backend results in a BadSMSConfigException a channel will be returned with backend_id=None """ backend_id = None phone_number_record = PhoneNumber.get_phone_number_for_owner(contact_id, phone_number) if phone_number_record: try: backend = phone_number_record.backend except BadSMSConfigException: backend = None if backend: backend_id = backend.couch_id return SMSChannel( backend_id=backend_id, phone_number=phone_number, )