def post(self): post_data = request.get_json() or request.form session_id = post_data.get('sessionId') phone_number = post_data.get('phoneNumber') user_input = post_data.get('text') service_code = post_data.get('serviceCode') if phone_number: user = get_user_by_phone(phone_number) # api chains all inputs that came through with * latest_input = user_input.split('*')[-1] if None in [user, session_id]: current_menu = UssdMenu.find_by_name('exit_not_registered') text = menu_display_text_in_lang(current_menu, user) else: current_menu = UssdProcessor.process_request(session_id, latest_input, user) ussd_session = create_or_update_session(session_id, user, current_menu, user_input, service_code) text = UssdProcessor.custom_display_text(current_menu, ussd_session) if "CON" not in text and "END" not in text: raise Exception("no menu found. text={}, user={}, menu={}, session={}".format(text, user.id, current_menu.name, ussd_session.id)) if len(text) > 164: print(f"Warning, text has length {len(text)}, display may be truncated") db.session.commit() else: current_menu = UssdMenu.find_by_name('exit_invalid_request') text = menu_display_text_in_lang(current_menu, None) return make_response(text), 200
def test_custom_display_text(test_client, init_database, menu_name, language, expecteds, real_at_idx, length, unexpected, menu_nr): with db.session.no_autoflush: start_state = UssdSessionFactory(state="menu_name") mapping = fake_transfer_mapping(length) real_usage1 = TransferUsage.find_or_create('Education') real_usage1.translations = {'en': 'Education', 'sw': 'Elimu'} real_usage2 = TransferUsage.find_or_create('Health') real_usage2.translations = {'en': 'Health', 'sw': 'Afya'} mapping[real_at_idx] = KenyaUssdStateMachine.make_usage_mapping(real_usage1) mapping[real_at_idx + 1] = KenyaUssdStateMachine.make_usage_mapping(real_usage2) user = standard_user() user.preferred_language = language start_state.session_data = { 'transfer_usage_mapping': mapping, 'usage_menu': menu_nr, 'usage_index_stack': [0, 8] } start_state.user = user menu = UssdMenu(name=menu_name, display_key="ussd.kenya.{}".format(menu_name)) resulting_menu = KenyaUssdProcessor.custom_display_text( menu, start_state) for expected in expecteds: assert expected in resulting_menu if unexpected is not None: assert unexpected not in resulting_menu
def test_menu_display_text_in_lang(test_client, init_database, user_factory, expected): user = user_factory() if user_factory else None menu = UssdMenu(display_key="ussd.sempo.exit_invalid_request") result = menu_display_text_in_lang(menu, user) assert result == expected
def process_request(session_id: str, user_input: str, user: User) -> UssdMenu: session: Optional[UssdSession] = UssdSession.query.filter_by( session_id=session_id).first() # returning session if session: if user_input == "": return UssdMenu.find_by_name('exit_invalid_input') elif user_input == '0': return UssdMenu.find_by_name(session.state).parent() else: new_state = KenyaUssdProcessor.next_state( session, user_input, user) return UssdMenu.find_by_name(new_state) # new session else: if user.has_valid_pin(): return UssdMenu.find_by_name('start') else: if user.failed_pin_attempts is not None and user.failed_pin_attempts >= 3: return UssdMenu.find_by_name('exit_pin_blocked') elif user.preferred_language is None: return UssdMenu.find_by_name('initial_language_selection') else: return UssdMenu.find_by_name('initial_pin_entry')
def test_create_or_update_session(test_client, init_database): from flask import g g.active_organisation = OrganisationFactory(country_code='AU') user = UserFactory(phone="123") # create a session in db menu3 = UssdMenu(id=3, name='foo', display_key='foo') db.session.add(menu3) session = UssdSession(session_id="1", user_id=user.id, msisdn="123", ussd_menu=menu3, state="foo", service_code="*123#") db.session.add(session) db.session.commit() # test updating existing create_or_update_session("1", user, UssdMenu(id=4, name="bar", display_key='bar'), "input", "*123#") sessions = UssdSession.query.filter_by(session_id="1") assert sessions.count() == 1 session = sessions.first() assert session.state == "bar" assert session.user_input == "input" assert session.ussd_menu_id == 4 # test creating a new one sessions = UssdSession.query.filter_by(session_id="2") assert sessions.count() == 0 create_or_update_session("2", user, UssdMenu(id=5, name="bat", display_key='bat'), "", "*123#") sessions = UssdSession.query.filter_by(session_id="2") assert sessions.count() == 1 session = sessions.first() assert session.state == "bat" assert session.user_input == "" assert session.ussd_menu_id == 5
def update_or_create_menu(name, description, parent_id=None): instance = UssdMenu.query.filter_by(name=name).first() if instance: instance.name = name instance.description = description instance.display_key = "ussd.kenya.{}".format(name) instance.parent_id = parent_id else: instance = UssdMenu(name=name, description=description, display_key="ussd.kenya.{}".format(name), parent_id=parent_id) db.session.add(instance) db.session.commit() return instance
def post(self): post_data = request.get_json() or request.form session_id = post_data.get('sessionId') phone_number = post_data.get('phoneNumber') user_input = post_data.get('text') service_code = post_data.get('serviceCode') # enforce only one single service code that can access the ussd state machine # through the endpoint if config.USSD_VALID_SERVICE_CODE != service_code: response = 'END ' response += i18n.t( 'ussd.kenya.invalid_service_code', valid_service_code=config.USSD_VALID_SERVICE_CODE, locale='sw') response += "\n" response += i18n.t( 'ussd.kenya.invalid_service_code', valid_service_code=config.USSD_VALID_SERVICE_CODE, locale='en') return make_response(response, 200) elif phone_number: user = get_user_by_phone(phone_number, 'KE') # api chains all inputs that came through with * latest_input = user_input.split('*')[-1] if None in [user, session_id]: user_without_transfer_account = create_user_without_transfer_account( phone_number) current_menu = UssdMenu.find_by_name( 'initial_language_selection') ussd_session = create_or_update_session( session_id=session_id, user=user_without_transfer_account, user_input=latest_input, service_code=service_code, current_menu=current_menu) text = KenyaUssdProcessor.custom_display_text( current_menu, ussd_session) else: current_menu = KenyaUssdProcessor.process_request( session_id, latest_input, user) ussd_session = create_or_update_session( session_id, user, current_menu, user_input, service_code) text = KenyaUssdProcessor.custom_display_text( current_menu, ussd_session) if "CON" not in text and "END" not in text: raise Exception( "no menu found. text={}, user={}, menu={}, session={}". format(text, user.id, current_menu.name, ussd_session.id)) if len(text) > 164: print( f"Warning, text has length {len(text)}, display may be truncated" ) db.session.commit() else: current_menu = UssdMenu.find_by_name('exit_invalid_request') text = menu_display_text_in_lang(current_menu, None) return make_response(text), 200