def test_fetch_exchange_rate(mocker, test_client, init_database, initialised_blockchain_network, user_type, preferred_language, exchange_text, limit_text): user = UserFactory(preferred_language=preferred_language, phone=phone()) if user_type == "group": user.set_held_role('GROUP_ACCOUNT', 'grassroots_group_account') user.is_phone_verified = True kyc = KycApplication(type='INDIVIDUAL') kyc.user = user kyc.kyc_status = 'VERIFIED' token1 = Token.query.filter_by(symbol="SM1").first() create_transfer_account_for_user(user, token1, 20000) def mock_convert(exchange_contract, from_token, to_token, from_amount): return from_amount * 1.2 mocker.patch('server.bt.get_conversion_amount', mock_convert) def mock_send_message(phone, message): assert exchange_text in message assert limit_text in message mocker.patch('server.utils.phone.send_message', mock_send_message) TokenProcessor.fetch_exchange_rate(user)
def test_agent_recipient(test_client, init_database, standard_user): session = send_enter_recipient_state() agent_recipient = UserFactory(phone=make_kenyan_phone(phone())) agent_recipient.set_held_role('TOKEN_AGENT', 'grassroots_token_agent') state_machine = KenyaUssdStateMachine(session, standard_user) state_machine.feed_char(agent_recipient.phone) assert state_machine.state == "exit_use_exchange_menu" assert session.session_data is None
def test_reset_pin_flow(test_client, init_database, create_organisation, authed_sempo_admin_user): # define different session id other_session_id = 'ATUid_05af06225e6163ec2dc9dc9cf8bc97aa000' # create organisation organisation = create_organisation organisation.external_auth_password = config.EXTERNAL_AUTH_PASSWORD # create highest tier admin admin = authed_sempo_admin_user admin.set_held_role('ADMIN', 'sempoadmin') # get admin's auth token auth = get_complete_auth_token(authed_sempo_admin_user) # create self signup user user = UserFactory(id=21, phone='+6185274136', first_name='Unknown first name', last_name='Unknown last name', pin_hash=User.salt_hash_secret('0000'), failed_pin_attempts=0, preferred_language="en", registration_method=RegistrationMethodEnum.USSD_SIGNUP) # bind user to organisation user.add_user_to_organisation(organisation, False) # reset pin as admin response = test_client.post('/api/v1/user/reset_pin/', headers=dict(Authorization=auth, Accept='application/json'), content_type='application/json', json={'user_id': user.id}) assert response.status_code == 200 assert len(user.pin_reset_tokens) > 0 # test user's reset process ussd_resp = req("", test_client, user.phone, session_id=other_session_id) assert "CON Please enter a PIN to manage your account." in ussd_resp ussd_resp = req("1212", test_client, user.phone, session_id=other_session_id) assert "CON Enter your PIN again" in ussd_resp ussd_resp = req("1212", test_client, user.phone, session_id=other_session_id) assert "CON Welcome to Sarafu" in ussd_resp
def test_agent_recipient(test_client, init_database): session = exchange_token_agent_number_entry_state() user = standard_user() user.phone = phone() agent_recipient = UserFactory(phone=make_kenyan_phone(phone())) agent_recipient.set_held_role('TOKEN_AGENT', 'grassroots_token_agent') state_machine = KenyaUssdStateMachine(session, user) state_machine.feed_char(agent_recipient.phone) assert state_machine.state == "exchange_token_amount_entry" assert session.get_data('agent_phone') == agent_recipient.phone
def test_send_token(mocker, test_client, init_database, initialised_blockchain_network, lang, token1_symbol, token2_symbol, recipient_balance, expected_send_msg, expected_receive_msg): org = OrganisationFactory() sender = UserFactory(preferred_language=lang, phone=phone(), first_name="Bob", last_name="Foo", default_organisation=org) token1 = Token.query.filter_by(symbol=token1_symbol).first() create_transfer_account_for_user(sender, token1, 20000) recipient = UserFactory(preferred_language=lang, phone=phone(), first_name="Joe", last_name="Bar", default_organisation=org) token2 = Token.query.filter_by(symbol=token2_symbol).first() create_transfer_account_for_user(recipient, token2, 30000) def mock_convert(exchange_contract, from_token, to_token, from_amount, signing_address): if from_token.symbol == "SM1": return from_amount * 1.5 else: return from_amount * 0.75 mocker.patch('server.bt.get_conversion_amount', mock_convert) messages = [] def mock_send_message(phone, message): messages.append({'phone': phone, 'message': message}) mocker.patch('server.message_processor.send_message', mock_send_message) TokenProcessor.send_token(sender, recipient, 1000, "A reason", 1) assert default_transfer_account(sender).balance == 19000 assert default_transfer_account(recipient).balance == recipient_balance assert len(messages) == 2 sent_message = messages[0] assert sent_message['phone'] == sender.phone assert expected_send_msg in sent_message['message'] received_message = messages[1] assert received_message['phone'] == recipient.phone assert expected_receive_msg in received_message['message']
def dl_processor(initialised_blockchain_network, create_transfer_usage): transfer_usage = create_transfer_usage user = UserFactory(phone=phone(), business_usage_id=transfer_usage.id) token = Token.query.filter_by(symbol="SM1").first() create_transfer_account_for_user(user, token, 200) return DirectoryListingProcessor(user, transfer_usage)
def test_send_welcome_sms(mocker, test_client, init_database, mock_sms_apis, preferred_language, org_key, expected_welcome, expected_terms): from flask import g token = TokenFactory(name='Sarafu', symbol='SARAFU') organisation = OrganisationFactory(custom_welcome_message_key=org_key, token=token, country_code='AU') g.active_organisation = organisation transfer_account = TransferAccountFactory(balance=10000, token=token, organisation=organisation) user = UserFactory(first_name='Magoo', phone='123456789', preferred_language=preferred_language, organisations=[organisation], default_organisation=organisation, transfer_accounts=[transfer_account]) send_onboarding_sms_messages(user) messages = mock_sms_apis assert messages == [ { 'phone': '+61123456789', 'message': expected_welcome }, { 'phone': '+61123456789', 'message': expected_terms }, ]
def test_send_balance_sms(mocker, test_client, init_database, initialised_blockchain_network, user_type, limit, preferred_language, sample_text): user = UserFactory(preferred_language=preferred_language, phone=phone()) if user_type == "group": user.set_held_role('GROUP_ACCOUNT', 'grassroots_group_account') user.is_phone_verified = True kyc = KycApplication(type='INDIVIDUAL') kyc.user = user kyc.kyc_status = 'VERIFIED' token1 = Token.query.filter_by(symbol="SM1").first() token2 = Token.query.filter_by(symbol="SM2").first() token3 = Token.query.filter_by(symbol="SM3").first() create_transfer_account_for_user(user, token1, 20000) create_transfer_account_for_user(user, token2, 35000, is_default=False) # this one should not show up in balance create_transfer_account_for_user(user, token3, 0, is_default=False, is_ghost=True) def mock_convert(exchange_contract, from_token, to_token, from_amount): if from_token.symbol == "SM1": return from_amount * 1.4124333344353534 else: return from_amount * 0.8398339289133232 mocker.patch('server.bt.get_conversion_amount', mock_convert) def mock_send_message(phone, message): assert sample_text in message assert "SM1 200" in message assert "SM2 350" in message assert "SM3" not in message if limit: assert "{:.2f} SM1 (1 SM1 = 1.41 AUD)".format(limit * 200) in message assert "{:.2f} SM2 (1 SM2 = 0.84 AUD)".format(limit * 350) in message mocker.patch('server.utils.phone.send_message', mock_send_message) TokenProcessor.send_balance_sms(user)
def test_standard_recipient(test_client, init_database, standard_user): session = send_enter_recipient_state() recipient_user = UserFactory(phone=make_kenyan_phone(phone())) state_machine = KenyaUssdStateMachine(session, standard_user) state_machine.feed_char(recipient_user.phone) assert state_machine.state == "send_token_amount" assert session.get_data('recipient_phone') == recipient_user.phone
def test_get_legacy_location(test_client, init_database, create_organisation, authed_sempo_admin_user, param_latitude, param_longitude, param_common_name): """ GIVEN coordinates and a name WHEN storing this to the legacy fields for location in db THEN they are retrievable through the legacy location http api call """ # create organisation organisation = create_organisation organisation.external_auth_password = config.EXTERNAL_AUTH_PASSWORD # create admin admin = authed_sempo_admin_user admin.set_held_role('ADMIN', 'admin') # create user with legacy location information user = UserFactory( id=42, lat=param_latitude, lng=param_longitude, _location=param_common_name, ) user.add_user_to_organisation(organisation, False) # get admin auth token auth = get_complete_auth_token(authed_sempo_admin_user) response = test_client.get( '/api/v2/geolocation/legacy/user/42/', headers=dict( Authorization=auth, Accept='application/json', ), ) assert response.status_code == 200 assert response.json['lat'] == param_latitude assert response.json['lng'] == param_longitude assert response.json['location'] == param_common_name
def test_user_recipient(test_client, init_database): session = exchange_token_agent_number_entry_state() user = standard_user() user.phone = phone() user_recipient = UserFactory(phone=make_kenyan_phone(phone())) state_machine = KenyaUssdStateMachine(session, user) state_machine.feed_char(user_recipient.phone) assert state_machine.state == "exit_invalid_token_agent" assert session.session_data is None
def test_invalid_service_code(mocker, test_client, init_database, initialised_blockchain_network, init_seed): org = OrganisationFactory() sender = UserFactory(preferred_language="en", phone=make_kenyan_phone(phone()), first_name="Bob", last_name="Foo", pin_hash=User.salt_hash_secret('0000'), default_organisation=org) resp = req("", test_client, sender.phone, '*42*666#') assert 'END Bonyeza {} kutumia mtandao'.format(valid_service_code) in resp
def test_send_directory_listing(mocker, test_client, init_database, dl_processor): user = dl_processor.recipient transfer_account = default_transfer_account(user) transfer_account.token.name = "A Token" user1 = UserFactory(phone=phone()) attrs = {"custom_attributes": {"bio": "some bio"}} set_custom_attributes(attrs, user1) user2 = UserFactory(phone=phone()) dl_processor.get_directory_listing_users = mocker.MagicMock( return_value=[user1, user2]) dl_processor.get_business_category_translation = mocker.MagicMock( return_value="Test") dl_processor.send_sms = mocker.MagicMock() dl_processor.send_directory_listing() dl_processor.send_sms.assert_called_with( 'send_directory_listing_message', community_token_name="A Token", business_type="Test", directory_listing_users=f"{user1.phone} some bio\n{user2.phone}")
def standard_user(test_client, init_database): from flask import g token = TokenFactory(name='Sarafu', symbol='SARAFU') organisation = OrganisationFactory(token=token, country_code='AU') g.active_organisation = organisation return UserFactory(first_name="Foo", last_name="Bar", pin_hash=User.salt_hash_secret('0000'), failed_pin_attempts=0, phone=phone(), default_organisation=organisation)
def test_exchange_token(mocker, test_client, init_database, initialised_blockchain_network): org = OrganisationFactory() sender = UserFactory(preferred_language="en", phone=phone(), first_name="Bob", last_name="Foo", default_organisation=org) sender.set_held_role('GROUP_ACCOUNT', 'grassroots_group_account') token1 = Token.query.filter_by(symbol="SM1").first() create_transfer_account_for_user(sender, token1, 20000) agent = UserFactory(phone=phone(), first_name="Joe", last_name="Bar", default_organisation=org) agent.set_held_role('TOKEN_AGENT', 'grassroots_token_agent') # this is under the assumption that token agent would have default token being the reserve token. is this the case? reserve = Token.query.filter_by(symbol="AUD").first() create_transfer_account_for_user(agent, reserve, 30000) def mock_convert(exchange_contract, from_token, to_token, from_amount, signing_address): return from_amount * 1.2 mocker.patch('server.bt.get_conversion_amount', mock_convert) messages = [] def mock_send_message(phone, message): messages.append({'phone': phone, 'message': message}) mocker.patch('server.message_processor.send_message', mock_send_message) TokenProcessor.exchange_token(sender, agent, 1000) assert default_transfer_account(sender).balance == 19000 assert default_transfer_account(agent).balance == 31200 assert len(messages) == 2 sent_message = messages[0] assert sent_message['phone'] == sender.phone assert 'sent a payment of 10.00 SM1 = 12.00 AUD' in sent_message['message'] received_message = messages[1] assert received_message['phone'] == agent.phone assert 'received a payment of 12.00 AUD = 10.00 SM1' in received_message[ 'message']
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 test_ussd_self_signup_wrong_pin_entry(test_client, init_database, create_temporary_user, create_organisation): # create organisation organisation = create_organisation organisation.external_auth_password = config.EXTERNAL_AUTH_PASSWORD # define username to match the external_auth_username as defined in : /app/migrations/versions/961ab9adc300_.py external_auth_username = '******' + (organisation.name or '').lower().replace(' ', '_') # set active organisation g.active_organisation = organisation other_unregistered_user_phone = '+611256465214' resp = req("", test_client, other_unregistered_user_phone) assert "CON Welcome to Sarafu" in resp # create self signup user user = UserFactory(id=22, phone=other_unregistered_user_phone, first_name='Unknown first name', last_name='Unknown last name', registration_method=RegistrationMethodEnum.USSD_SIGNUP) resp = req("1", test_client, user.phone, auth_username=external_auth_username) assert "CON Please enter a PIN" in resp resp = req("0000", test_client, user.phone, auth_username=external_auth_username) assert "CON Enter your PIN again" in resp resp = req("1212", test_client, user.phone, auth_username=external_auth_username) assert "END The new PIN does not match the one you entered." in resp
def test_send_welcome_sms(mocker, test_client, init_database, preferred_language, org_key, expected_welcome, expected_terms): from flask import g token = TokenFactory(name='Sarafu', symbol='SARAFU') organisation = OrganisationFactory(custom_welcome_message_key=org_key, token=token, country_code='AU') g.active_organisation = organisation transfer_account = TransferAccountFactory(balance=10000, token=token, organisation=organisation) user = UserFactory(first_name='Magoo', phone='123456789', preferred_language=preferred_language, organisations=[organisation], default_organisation=organisation, transfer_accounts=[transfer_account]) send_message = mocker.MagicMock() mocker.patch('server.message_processor.send_message', send_message) send_onboarding_sms_messages(user) send_message.assert_has_calls( [mocker.call('+61123456789', expected_welcome), mocker.call('+61123456789', expected_terms)])
def create_user(cat, token, is_market_enabled): _user = UserFactory(phone=phone(), business_usage_id=cat.id, is_market_enabled=is_market_enabled) create_transfer_account_for_user(_user, token, 200) return _user
def test_golden_path_send_token(mocker, test_client, init_database, initialised_blockchain_network, init_seed): token = Token.query.filter_by(symbol="SM1").first() org = OrganisationFactory(country_code=config.DEFAULT_COUNTRY) sender = UserFactory(preferred_language="en", phone='+6110011223344', first_name="Bob", last_name="Foo", pin_hash=User.salt_hash_secret('0000'), default_organisation=org) create_transfer_account_for_user(sender, token, 4220) recipient = UserFactory(preferred_language="sw", phone='+6114433221100', first_name="Joe", last_name="Bar", default_organisation=org) create_transfer_account_for_user(recipient, token, 1980) usages = TransferUsage.query.filter_by(default=True).order_by( TransferUsage.priority).all() top_priority = usages[0] # Take the last to ensure that we're not going to simply reinforce the existing order usage = usages[-1] # do two of these transfers to ensure last is is the first shown make_payment_transfer(100, token=token, send_user=sender, receive_user=recipient, transfer_use=str(int(usage.id)), is_ghost_transfer=False, require_sender_approved=False, require_recipient_approved=False) make_payment_transfer(100, token=token, send_user=sender, receive_user=recipient, transfer_use=str(int(usage.id)), is_ghost_transfer=False, require_sender_approved=False, require_recipient_approved=False) def mock_send_message(phone, message): messages.append({'phone': phone, 'message': message}) mocker.patch(f'server.utils.phone._send_twilio_message.submit', mock_send_message) mocker.patch(f'server.utils.phone._send_messagebird_message.submit', mock_send_message) mocker.patch(f'server.utils.phone._send_at_message.submit', mock_send_message) assert get_session() is None resp = req("", test_client, sender.phone) assert get_session() is not None assert "CON Welcome" in resp resp = req("1", test_client, sender.phone) assert "CON Enter Phone" in resp resp = req(recipient.phone, test_client, sender.phone) assert "CON Enter Amount" in resp resp = req("12.5", test_client, sender.phone) assert f"{recipient.user_details()} will receive 12.5 {token.symbol} from {sender.user_details()}" in resp resp = req("0000", test_client, sender.phone) assert "END Your request has been sent." in resp assert default_transfer_account(sender).balance == (4220 - 100 - 100 - 1250) assert default_transfer_account(recipient).balance == (1980 + 100 + 100 + 1250) assert len(messages) == 2 sent_message = messages[0] assert sent_message['phone'] == sender.phone assert f"sent 12 SM1 to {recipient.first_name}" in sent_message['message'] received_message = messages[1] assert received_message['phone'] == recipient.phone assert f"Umepokea 12 SM1 kutoka kwa {sender.first_name}" in received_message[ 'message']
def test_golden_path_send_token(mocker, test_client, init_database, initialised_blockchain_network, init_seed): token = Token.query.filter_by(symbol="SM1").first() org = OrganisationFactory(country_code='KE') sender = UserFactory(preferred_language="en", phone=make_kenyan_phone(phone()), first_name="Bob", last_name="Foo", pin_hash=User.salt_hash_secret('0000'), default_organisation=org) create_transfer_account_for_user(sender, token, 4220) recipient = UserFactory(preferred_language="sw", phone=make_kenyan_phone(phone()), first_name="Joe", last_name="Bar", default_organisation=org) create_transfer_account_for_user(recipient, token, 1980) messages = [] session_id = 'ATUid_05af06225e6163ec2dc9dc9cf8bc97aa' usages = TransferUsage.query.filter_by(default=True).order_by( TransferUsage.priority).all() top_priority = usages[0] # Take the last to ensure that we're not going to simply reinforce the existing order usage = usages[-1] # do two of these transfers to ensure last is is the first shown make_payment_transfer(100, token=token, send_user=sender, receive_user=recipient, transfer_use=str(int(usage.id)), is_ghost_transfer=False, require_sender_approved=False, require_recipient_approved=False) make_payment_transfer(100, token=token, send_user=sender, receive_user=recipient, transfer_use=str(int(usage.id)), is_ghost_transfer=False, require_sender_approved=False, require_recipient_approved=False) def mock_send_message(phone, message): messages.append({'phone': phone, 'message': message}) mocker.patch(f'server.utils.phone._send_twilio_message.submit', mock_send_message) mocker.patch(f'server.utils.phone._send_messagebird_message.submit', mock_send_message) mocker.patch(f'server.utils.phone._send_at_message.submit', mock_send_message) def req(text): response = test_client.post( f'/api/v1/ussd/kenya?username={config.EXTERNAL_AUTH_USERNAME}&password={config.EXTERNAL_AUTH_PASSWORD}', headers=dict(Accept='application/json'), json={ 'sessionId': session_id, 'phoneNumber': sender.phone, 'text': text, 'serviceCode': '*384*23216#' }) assert response.status_code == 200 return response.data.decode("utf-8") def get_session(): return UssdSession.query.filter_by(session_id=session_id).first() assert get_session() is None resp = req("") assert get_session() is not None assert "CON Welcome" in resp resp = req("1") assert "CON Enter Phone" in resp resp = req(recipient.phone) assert "CON Enter Amount" in resp resp = req("12.5") assert "CON Transfer Reason" in resp assert f"1. {top_priority.translations['en']}" in resp assert "9." in resp resp = req("9") assert "CON Please specify" in resp assert "10. Show previous" in resp assert "9." not in resp resp = req("10") resp = req("4") assert "CON Please enter your PIN" in resp resp = req("0000") assert "CON Send 12.5 SM1" in resp # went to second page, should not be the first assert f"for {top_priority.translations['en']}" not in resp resp = req("1") assert "END Your request has been sent." in resp assert default_transfer_account(sender).balance == (4220 - 100 - 100 - 1250) assert default_transfer_account(recipient).balance == (1980 + 100 + 100 + 1250) assert len(messages) == 3 sent_message = messages[1] assert sent_message['phone'] == sender.phone assert f"sent a payment of 12.50 SM1 to {recipient.first_name}" in sent_message[ 'message'] received_message = messages[2] assert received_message['phone'] == recipient.phone assert f"Umepokea 12.50 SM1 kutoka kwa {sender.first_name}" in received_message[ 'message']