Compare commits
16 Commits
philip/cle
...
master
Author | SHA1 | Date | |
---|---|---|---|
3dcfbe59fe | |||
0dd21f3970 | |||
6ca8632cde | |||
b7dc290992 | |||
e69801ea08 | |||
50a596e707 | |||
cb61e45e4c | |||
e5b06b18d7 | |||
|
5d1a30021a | ||
|
ade3f4e917 | ||
c3e924ae8f | |||
ff4c42dc24 | |||
b8cd7eec56 | |||
837e8da650 | |||
fe3f2c2549 | |||
46f25e5678 |
@ -15,5 +15,5 @@ To get started see [./apps/contract-migration/README.md](./apps/contract-migrati
|
||||
|
||||
## Documentation
|
||||
|
||||
[https://docs.grassecon.org/cic_stack/](https://docs.grassecon.org/cic_stack/)
|
||||
[https://docs.grassecon.org/software/](https://docs.grassecon.org/software/)
|
||||
|
||||
|
@ -17,7 +17,7 @@ from cic_eth_registry.error import UnknownContractError
|
||||
# local imports
|
||||
from cic_eth.error import SeppukuError
|
||||
from cic_eth.db.models.base import SessionBase
|
||||
from cic_eth.eth.util import CacheGasOracle
|
||||
from cic_eth.eth.util import CacheGasOracle, MaxGasOracle
|
||||
|
||||
#logg = logging.getLogger().getChild(__name__)
|
||||
logg = logging.getLogger()
|
||||
@ -41,21 +41,24 @@ class BaseTask(celery.Task):
|
||||
|
||||
|
||||
def create_gas_oracle(self, conn, address=None, *args, **kwargs):
|
||||
if address == None:
|
||||
return RPCGasOracle(
|
||||
x = None
|
||||
if address is None:
|
||||
x = RPCGasOracle(
|
||||
conn,
|
||||
code_callback=kwargs.get('code_callback', self.get_min_fee_limit),
|
||||
min_price=self.min_fee_price,
|
||||
id_generator=kwargs.get('id_generator'),
|
||||
)
|
||||
else:
|
||||
|
||||
return CacheGasOracle(
|
||||
conn,
|
||||
address,
|
||||
method=kwargs.get('method'),
|
||||
min_price=self.min_fee_price,
|
||||
id_generator=kwargs.get('id_generator'),
|
||||
)
|
||||
x = MaxGasOracle(conn)
|
||||
x.code_callback = x.get_fee_units
|
||||
|
||||
return x
|
||||
|
||||
|
||||
def get_min_fee_limit(self, code):
|
||||
return self.min_fee_limit
|
||||
|
||||
|
||||
def get_min_fee_limit(self, code):
|
||||
|
@ -1,10 +1,9 @@
|
||||
[DATABASE]
|
||||
user = postgres
|
||||
password =
|
||||
host = localhost
|
||||
port = 5432
|
||||
name = /tmp/cic-notify.db
|
||||
#engine = postgresql
|
||||
#driver = psycopg2
|
||||
engine = sqlite
|
||||
driver = pysqlite
|
||||
[database]
|
||||
name=cic_notify_test
|
||||
user=
|
||||
password=
|
||||
host=localhost
|
||||
port=
|
||||
engine=sqlite
|
||||
driver=pysqlite
|
||||
debug=0
|
||||
|
7
apps/cic-notify/.coveragerc
Normal file
7
apps/cic-notify/.coveragerc
Normal file
@ -0,0 +1,7 @@
|
||||
[report]
|
||||
omit =
|
||||
venv/*
|
||||
scripts/*
|
||||
cic_notify/db/migrations/*
|
||||
cic_notify/runnable/*
|
||||
cic_notify/version.py
|
@ -3,6 +3,7 @@ import logging
|
||||
import re
|
||||
|
||||
# third-party imports
|
||||
import cic_notify.tasks.sms.db
|
||||
from celery.app.control import Inspect
|
||||
import celery
|
||||
|
||||
@ -13,45 +14,16 @@ app = celery.current_app
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
|
||||
sms_tasks_matcher = r"^(cic_notify.tasks.sms)(\.\w+)?"
|
||||
|
||||
|
||||
re_q = r'^cic-notify'
|
||||
def get_sms_queue_tasks(app, task_prefix='cic_notify.tasks.sms.'):
|
||||
host_queues = []
|
||||
|
||||
i = Inspect(app=app)
|
||||
qs = i.active_queues()
|
||||
for host in qs.keys():
|
||||
for q in qs[host]:
|
||||
if re.match(re_q, q['name']):
|
||||
host_queues.append((host, q['name'],))
|
||||
|
||||
task_prefix_len = len(task_prefix)
|
||||
queue_tasks = []
|
||||
for (host, queue) in host_queues:
|
||||
i = Inspect(app=app, destination=[host])
|
||||
for tasks in i.registered_tasks().values():
|
||||
for task in tasks:
|
||||
if len(task) >= task_prefix_len and task[:task_prefix_len] == task_prefix:
|
||||
queue_tasks.append((queue, task,))
|
||||
|
||||
return queue_tasks
|
||||
|
||||
|
||||
class Api:
|
||||
# TODO: Implement callback strategy
|
||||
def __init__(self, queue=None):
|
||||
def __init__(self, queue: any = 'cic-notify'):
|
||||
"""
|
||||
:param queue: The queue on which to execute notification tasks
|
||||
:type queue: str
|
||||
"""
|
||||
self.queue = queue
|
||||
self.sms_tasks = get_sms_queue_tasks(app)
|
||||
logg.debug('sms tasks {}'.format(self.sms_tasks))
|
||||
|
||||
|
||||
def sms(self, message, recipient):
|
||||
def sms(self, message: str, recipient: str):
|
||||
"""This function chains all sms tasks in order to send a message, log and persist said data to disk
|
||||
:param message: The message to be sent to the recipient.
|
||||
:type message: str
|
||||
@ -60,24 +32,9 @@ class Api:
|
||||
:return: a celery Task
|
||||
:rtype: Celery.Task
|
||||
"""
|
||||
signatures = []
|
||||
for q in self.sms_tasks:
|
||||
|
||||
if not self.queue:
|
||||
queue = q[0]
|
||||
else:
|
||||
queue = self.queue
|
||||
|
||||
signature = celery.signature(
|
||||
q[1],
|
||||
[
|
||||
message,
|
||||
recipient,
|
||||
],
|
||||
queue=queue,
|
||||
)
|
||||
signatures.append(signature)
|
||||
|
||||
t = celery.group(signatures)()
|
||||
|
||||
return t
|
||||
s_send = celery.signature('cic_notify.tasks.sms.africastalking.send', [message, recipient], queue=self.queue)
|
||||
s_log = celery.signature('cic_notify.tasks.sms.log.log', [message, recipient], queue=self.queue)
|
||||
s_persist_notification = celery.signature(
|
||||
'cic_notify.tasks.sms.db.persist_notification', [message, recipient], queue=self.queue)
|
||||
signatures = [s_send, s_log, s_persist_notification]
|
||||
return celery.group(signatures)()
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
script_location = migrations
|
||||
script_location = .
|
||||
|
||||
# template used to generate migration files
|
||||
# file_template = %%(rev)s_%%(slug)s
|
||||
@ -27,28 +27,17 @@ script_location = migrations
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; this defaults
|
||||
# to migrations/versions. When using multiple version
|
||||
# to ./versions. When using multiple version
|
||||
# directories, initial revisions must be specified with --version-path
|
||||
# version_locations = %(here)s/bar %(here)s/bat migrations/versions
|
||||
# version_locations = %(here)s/bar %(here)s/bat ./versions
|
||||
|
||||
# the output encoding used when revision files
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
sqlalchemy.url = postgres+psycopg2://postgres@localhost/cic-notify
|
||||
sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||
|
||||
|
||||
[post_write_hooks]
|
||||
# post_write_hooks defines scripts or Python functions that are run
|
||||
# on newly generated revision scripts. See the documentation for further
|
||||
# detail and examples
|
||||
|
||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
||||
# hooks=black
|
||||
# black.type=console_scripts
|
||||
# black.entrypoint=black
|
||||
# black.options=-l 79
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
@ -11,7 +11,7 @@ config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
fileConfig(config.config_file_name)
|
||||
fileConfig(config.config_file_name, disable_existing_loggers=True)
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
@ -56,8 +56,11 @@ def run_migrations_online():
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
connectable = context.config.attributes.get("connection", None)
|
||||
|
||||
if connectable is None:
|
||||
connectable = engine_from_config(
|
||||
config.get_section(config.config_ini_section),
|
||||
context.config.get_section(context.config.config_ini_section),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
@ -7,7 +7,7 @@ import celery
|
||||
|
||||
celery_app = celery.current_app
|
||||
logg = celery_app.log.get_default_logger()
|
||||
local_logg = logging.getLogger(__name__)
|
||||
local_logg = logging.getLogger()
|
||||
|
||||
|
||||
@celery_app.task
|
||||
|
@ -1,5 +1,9 @@
|
||||
pytest~=6.0.1
|
||||
pytest-celery~=0.0.0a1
|
||||
pytest-mock~=3.3.1
|
||||
pysqlite3~=0.4.3
|
||||
pytest-cov==2.10.1
|
||||
Faker==11.1.0
|
||||
faker-e164==0.1.0
|
||||
pytest==6.2.5
|
||||
pytest-celery~=0.0.0
|
||||
pytest-mock==3.6.1
|
||||
pysqlite3~=0.4.6
|
||||
pytest-cov==3.0.0
|
||||
pytest-alembic==0.7.0
|
||||
requests-mock==1.9.3
|
||||
|
@ -0,0 +1,28 @@
|
||||
import pytest
|
||||
|
||||
|
||||
def test_single_head_revision(alembic_runner):
|
||||
heads = alembic_runner.heads
|
||||
head_count = len(heads)
|
||||
assert head_count == 1
|
||||
|
||||
|
||||
def test_upgrade(alembic_runner):
|
||||
try:
|
||||
alembic_runner.migrate_up_to("head")
|
||||
except RuntimeError:
|
||||
pytest.fail('Failed to upgrade to the head revision.')
|
||||
|
||||
|
||||
def test_up_down_consistency(alembic_runner):
|
||||
try:
|
||||
for revision in alembic_runner.history.revisions:
|
||||
alembic_runner.migrate_up_to(revision)
|
||||
except RuntimeError:
|
||||
pytest.fail('Failed to upgrade through each revision individually.')
|
||||
|
||||
try:
|
||||
for revision in reversed(alembic_runner.history.revisions):
|
||||
alembic_runner.migrate_down_to(revision)
|
||||
except RuntimeError:
|
||||
pytest.fail('Failed to downgrade through each revision individually.')
|
@ -0,0 +1,27 @@
|
||||
# standard imports
|
||||
|
||||
# external imports
|
||||
from faker import Faker
|
||||
from faker_e164.providers import E164Provider
|
||||
|
||||
# local imports
|
||||
from cic_notify.db.enum import NotificationStatusEnum, NotificationTransportEnum
|
||||
from cic_notify.db.models.notification import Notification
|
||||
|
||||
|
||||
# test imports
|
||||
from tests.helpers.phone import phone_number
|
||||
|
||||
|
||||
def test_notification(init_database):
|
||||
message = 'Hello world'
|
||||
recipient = phone_number()
|
||||
notification = Notification(NotificationTransportEnum.SMS, recipient, message)
|
||||
init_database.add(notification)
|
||||
init_database.commit()
|
||||
|
||||
notification = init_database.query(Notification).get(1)
|
||||
assert notification.status == NotificationStatusEnum.UNKNOWN
|
||||
assert notification.recipient == recipient
|
||||
assert notification.message == message
|
||||
assert notification.transport == NotificationTransportEnum.SMS
|
38
apps/cic-notify/tests/cic_notify/db/test_db.py
Normal file
38
apps/cic-notify/tests/cic_notify/db/test_db.py
Normal file
@ -0,0 +1,38 @@
|
||||
# standard imports
|
||||
import os
|
||||
|
||||
# third-party imports
|
||||
|
||||
# local imports
|
||||
from cic_notify.db import dsn_from_config
|
||||
|
||||
|
||||
def test_dsn_from_config(load_config):
|
||||
"""
|
||||
"""
|
||||
# test dsn for other db formats
|
||||
overrides = {
|
||||
'DATABASE_PASSWORD': 'password',
|
||||
'DATABASE_DRIVER': 'psycopg2',
|
||||
'DATABASE_ENGINE': 'postgresql'
|
||||
}
|
||||
load_config.dict_override(dct=overrides, dct_description='Override values to test different db formats.')
|
||||
|
||||
scheme = f'{load_config.get("DATABASE_ENGINE")}+{load_config.get("DATABASE_DRIVER")}'
|
||||
|
||||
dsn = dsn_from_config(load_config)
|
||||
assert dsn == f"{scheme}://{load_config.get('DATABASE_USER')}:{load_config.get('DATABASE_PASSWORD')}@{load_config.get('DATABASE_HOST')}:{load_config.get('DATABASE_PORT')}/{load_config.get('DATABASE_NAME')}"
|
||||
|
||||
# undoes overrides to revert engine and drivers to sqlite
|
||||
overrides = {
|
||||
'DATABASE_PASSWORD': '',
|
||||
'DATABASE_DRIVER': 'pysqlite',
|
||||
'DATABASE_ENGINE': 'sqlite'
|
||||
}
|
||||
load_config.dict_override(dct=overrides, dct_description='Override values to test different db formats.')
|
||||
|
||||
# test dsn for sqlite engine
|
||||
dsn = dsn_from_config(load_config)
|
||||
scheme = f'{load_config.get("DATABASE_ENGINE")}+{load_config.get("DATABASE_DRIVER")}'
|
||||
assert dsn == f'{scheme}:///{load_config.get("DATABASE_NAME")}'
|
||||
|
@ -0,0 +1,75 @@
|
||||
# standard imports
|
||||
import logging
|
||||
import os
|
||||
|
||||
# external imports
|
||||
import pytest
|
||||
import requests_mock
|
||||
|
||||
|
||||
# local imports
|
||||
from cic_notify.error import NotInitializedError, AlreadyInitializedError, NotificationSendError
|
||||
from cic_notify.tasks.sms.africastalking import AfricasTalkingNotifier
|
||||
|
||||
# test imports
|
||||
from tests.helpers.phone import phone_number
|
||||
|
||||
|
||||
def test_africas_talking_notifier(africastalking_response, caplog):
|
||||
caplog.set_level(logging.DEBUG)
|
||||
with pytest.raises(NotInitializedError) as error:
|
||||
AfricasTalkingNotifier()
|
||||
assert str(error.value) == ''
|
||||
|
||||
api_key = os.urandom(24).hex()
|
||||
sender_id = 'bar'
|
||||
username = 'sandbox'
|
||||
AfricasTalkingNotifier.initialize(username, api_key, sender_id)
|
||||
africastalking_notifier = AfricasTalkingNotifier()
|
||||
assert africastalking_notifier.sender_id == sender_id
|
||||
assert africastalking_notifier.initiated is True
|
||||
|
||||
with pytest.raises(AlreadyInitializedError) as error:
|
||||
AfricasTalkingNotifier.initialize(username, api_key, sender_id)
|
||||
assert str(error.value) == ''
|
||||
|
||||
with requests_mock.Mocker(real_http=False) as request_mocker:
|
||||
message = 'Hello world.'
|
||||
recipient = phone_number()
|
||||
africastalking_response.get('SMSMessageData').get('Recipients')[0]['number'] = recipient
|
||||
request_mocker.register_uri(method='POST',
|
||||
headers={'content-type': 'application/json'},
|
||||
json=africastalking_response,
|
||||
url='https://api.sandbox.africastalking.com/version1/messaging',
|
||||
status_code=200)
|
||||
africastalking_notifier.send(message, recipient)
|
||||
assert f'Africastalking response sender-id {africastalking_response}' in caplog.text
|
||||
africastalking_notifier.sender_id = None
|
||||
africastalking_notifier.send(message, recipient)
|
||||
assert f'africastalking response no-sender-id {africastalking_response}' in caplog.text
|
||||
with pytest.raises(NotificationSendError) as error:
|
||||
status = 'InvalidPhoneNumber'
|
||||
status_code = 403
|
||||
africastalking_response.get('SMSMessageData').get('Recipients')[0]['status'] = status
|
||||
africastalking_response.get('SMSMessageData').get('Recipients')[0]['statusCode'] = status_code
|
||||
|
||||
request_mocker.register_uri(method='POST',
|
||||
headers={'content-type': 'application/json'},
|
||||
json=africastalking_response,
|
||||
url='https://api.sandbox.africastalking.com/version1/messaging',
|
||||
status_code=200)
|
||||
africastalking_notifier.send(message, recipient)
|
||||
assert str(error.value) == f'Sending notification failed due to: {status}'
|
||||
with pytest.raises(NotificationSendError) as error:
|
||||
recipients = []
|
||||
status = 'InsufficientBalance'
|
||||
africastalking_response.get('SMSMessageData')['Recipients'] = recipients
|
||||
africastalking_response.get('SMSMessageData')['Message'] = status
|
||||
|
||||
request_mocker.register_uri(method='POST',
|
||||
headers={'content-type': 'application/json'},
|
||||
json=africastalking_response,
|
||||
url='https://api.sandbox.africastalking.com/version1/messaging',
|
||||
status_code=200)
|
||||
africastalking_notifier.send(message, recipient)
|
||||
assert str(error.value) == f'Unexpected number of recipients: {len(recipients)}. Status: {status}'
|
26
apps/cic-notify/tests/cic_notify/tasks/sms/test_db_tasks.py
Normal file
26
apps/cic-notify/tests/cic_notify/tasks/sms/test_db_tasks.py
Normal file
@ -0,0 +1,26 @@
|
||||
# standard imports
|
||||
|
||||
# external imports
|
||||
import celery
|
||||
|
||||
# local imports
|
||||
from cic_notify.db.enum import NotificationStatusEnum, NotificationTransportEnum
|
||||
from cic_notify.db.models.notification import Notification
|
||||
|
||||
# test imports
|
||||
from tests.helpers.phone import phone_number
|
||||
|
||||
|
||||
def test_persist_notification(celery_session_worker, init_database):
|
||||
message = 'Hello world.'
|
||||
recipient = phone_number()
|
||||
s_persist_notification = celery.signature(
|
||||
'cic_notify.tasks.sms.db.persist_notification', (message, recipient)
|
||||
)
|
||||
s_persist_notification.apply_async().get()
|
||||
|
||||
notification = Notification.session.query(Notification).filter_by(recipient=recipient).first()
|
||||
assert notification.status == NotificationStatusEnum.UNKNOWN
|
||||
assert notification.recipient == recipient
|
||||
assert notification.message == message
|
||||
assert notification.transport == NotificationTransportEnum.SMS
|
21
apps/cic-notify/tests/cic_notify/tasks/sms/test_log_tasks.py
Normal file
21
apps/cic-notify/tests/cic_notify/tasks/sms/test_log_tasks.py
Normal file
@ -0,0 +1,21 @@
|
||||
# standard imports
|
||||
import logging
|
||||
|
||||
# external imports
|
||||
import celery
|
||||
|
||||
# local imports
|
||||
|
||||
# test imports
|
||||
from tests.helpers.phone import phone_number
|
||||
|
||||
|
||||
def test_log(caplog, celery_session_worker):
|
||||
message = 'Hello world.'
|
||||
recipient = phone_number()
|
||||
caplog.set_level(logging.INFO)
|
||||
s_log = celery.signature(
|
||||
'cic_notify.tasks.sms.log.log', [message, recipient]
|
||||
)
|
||||
s_log.apply_async().get()
|
||||
assert f'message to {recipient}: {message}' in caplog.text
|
24
apps/cic-notify/tests/cic_notify/test_api.py
Normal file
24
apps/cic-notify/tests/cic_notify/test_api.py
Normal file
@ -0,0 +1,24 @@
|
||||
# standard imports
|
||||
|
||||
# external imports
|
||||
import celery
|
||||
|
||||
# local imports
|
||||
from cic_notify.api import Api
|
||||
|
||||
# test imports
|
||||
from tests.helpers.phone import phone_number
|
||||
|
||||
|
||||
def test_api(celery_session_worker, mocker):
|
||||
mocked_group = mocker.patch('celery.group')
|
||||
message = 'Hello world.'
|
||||
recipient = phone_number()
|
||||
s_send = celery.signature('cic_notify.tasks.sms.africastalking.send', [message, recipient], queue=None)
|
||||
s_log = celery.signature('cic_notify.tasks.sms.log.log', [message, recipient], queue=None)
|
||||
s_persist_notification = celery.signature(
|
||||
'cic_notify.tasks.sms.db.persist_notification', [message, recipient], queue=None)
|
||||
signatures = [s_send, s_log, s_persist_notification]
|
||||
api = Api(queue=None)
|
||||
api.sms(message, recipient)
|
||||
mocked_group.assert_called_with(signatures)
|
@ -1,31 +1,13 @@
|
||||
# standard imports
|
||||
import sys
|
||||
import os
|
||||
import pytest
|
||||
import logging
|
||||
|
||||
# third party imports
|
||||
import confini
|
||||
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
root_dir = os.path.dirname(script_dir)
|
||||
sys.path.insert(0, root_dir)
|
||||
|
||||
# local imports
|
||||
from cic_notify.db.models.base import SessionBase
|
||||
#from transport.notification import AfricastalkingNotification
|
||||
|
||||
# fixtures
|
||||
from tests.fixtures_config import *
|
||||
from tests.fixtures_celery import *
|
||||
from tests.fixtures_database import *
|
||||
# test imports
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
#@pytest.fixture(scope='session')
|
||||
#def africastalking_notification(
|
||||
# load_config,
|
||||
# ):
|
||||
# return AfricastalkingNotificationTransport(load_config)
|
||||
#
|
||||
from .fixtures.celery import *
|
||||
from .fixtures.config import *
|
||||
from .fixtures.database import *
|
||||
from .fixtures.result import *
|
||||
|
@ -37,12 +37,6 @@ def celery_config():
|
||||
shutil.rmtree(rq)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def celery_worker_parameters():
|
||||
return {
|
||||
# 'queues': ('cic-notify'),
|
||||
}
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def celery_enable_logging():
|
||||
return True
|
32
apps/cic-notify/tests/fixtures/config.py
vendored
Normal file
32
apps/cic-notify/tests/fixtures/config.py
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
# standard imports
|
||||
import os
|
||||
import logging
|
||||
|
||||
# external imports
|
||||
import pytest
|
||||
from confini import Config
|
||||
|
||||
logg = logging.getLogger(__file__)
|
||||
|
||||
|
||||
fixtures_dir = os.path.dirname(__file__)
|
||||
root_directory = os.path.dirname(os.path.dirname(fixtures_dir))
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def alembic_config():
|
||||
migrations_directory = os.path.join(root_directory, 'cic_notify', 'db', 'migrations', 'default')
|
||||
file = os.path.join(migrations_directory, 'alembic.ini')
|
||||
return {
|
||||
'file': file,
|
||||
'script_location': migrations_directory
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def load_config():
|
||||
config_directory = os.path.join(root_directory, '.config/test')
|
||||
config = Config(default_dir=config_directory)
|
||||
config.process()
|
||||
logg.debug('config loaded\n{}'.format(config))
|
||||
return config
|
54
apps/cic-notify/tests/fixtures/database.py
vendored
Normal file
54
apps/cic-notify/tests/fixtures/database.py
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
# standard imports
|
||||
import os
|
||||
|
||||
# third-party imports
|
||||
import pytest
|
||||
import alembic
|
||||
from alembic.config import Config as AlembicConfig
|
||||
|
||||
# local imports
|
||||
from cic_notify.db import dsn_from_config
|
||||
from cic_notify.db.models.base import SessionBase, create_engine
|
||||
|
||||
from .config import root_directory
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def alembic_engine(load_config):
|
||||
data_source_name = dsn_from_config(load_config)
|
||||
return create_engine(data_source_name)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def database_engine(load_config):
|
||||
if load_config.get('DATABASE_ENGINE') == 'sqlite':
|
||||
try:
|
||||
os.unlink(load_config.get('DATABASE_NAME'))
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
dsn = dsn_from_config(load_config)
|
||||
SessionBase.connect(dsn)
|
||||
return dsn
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def init_database(load_config, database_engine):
|
||||
db_directory = os.path.join(root_directory, 'cic_notify', 'db')
|
||||
migrations_directory = os.path.join(db_directory, 'migrations', load_config.get('DATABASE_ENGINE'))
|
||||
if not os.path.isdir(migrations_directory):
|
||||
migrations_directory = os.path.join(db_directory, 'migrations', 'default')
|
||||
|
||||
session = SessionBase.create_session()
|
||||
|
||||
alembic_config = AlembicConfig(os.path.join(migrations_directory, 'alembic.ini'))
|
||||
alembic_config.set_main_option('sqlalchemy.url', database_engine)
|
||||
alembic_config.set_main_option('script_location', migrations_directory)
|
||||
|
||||
alembic.command.downgrade(alembic_config, 'base')
|
||||
alembic.command.upgrade(alembic_config, 'head')
|
||||
|
||||
yield session
|
||||
session.commit()
|
||||
session.close()
|
||||
|
||||
|
24
apps/cic-notify/tests/fixtures/result.py
vendored
Normal file
24
apps/cic-notify/tests/fixtures/result.py
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# standard imports
|
||||
|
||||
# external imports
|
||||
import pytest
|
||||
|
||||
|
||||
# local imports
|
||||
|
||||
# test imports
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def africastalking_response():
|
||||
return {
|
||||
"SMSMessageData": {
|
||||
"Message": "Sent to 1/1 Total Cost: KES 0.8000",
|
||||
"Recipients": [{
|
||||
"statusCode": 101,
|
||||
"number": "+254711XXXYYY",
|
||||
"status": "Success",
|
||||
"cost": "KES 0.8000",
|
||||
"messageId": "ATPid_SampleTxnId123"
|
||||
}]
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
# standard imports
|
||||
import os
|
||||
import logging
|
||||
|
||||
# third-party imports
|
||||
import pytest
|
||||
import confini
|
||||
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
root_dir = os.path.dirname(script_dir)
|
||||
logg = logging.getLogger(__file__)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def load_config():
|
||||
config_dir = os.path.join(root_dir, '.config/test')
|
||||
conf = confini.Config(config_dir, 'CICTEST')
|
||||
conf.process()
|
||||
logg.debug('config {}'.format(conf))
|
||||
return conf
|
@ -1,48 +0,0 @@
|
||||
# standard imports
|
||||
import os
|
||||
|
||||
# third-party imports
|
||||
import pytest
|
||||
import alembic
|
||||
from alembic.config import Config as AlembicConfig
|
||||
|
||||
# local imports
|
||||
from cic_notify.db import SessionBase
|
||||
from cic_notify.db import dsn_from_config
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def database_engine(
|
||||
load_config,
|
||||
):
|
||||
dsn = dsn_from_config(load_config)
|
||||
SessionBase.connect(dsn)
|
||||
return dsn
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def init_database(
|
||||
load_config,
|
||||
database_engine,
|
||||
):
|
||||
|
||||
rootdir = os.path.dirname(os.path.dirname(__file__))
|
||||
dbdir = os.path.join(rootdir, 'cic_notify', 'db')
|
||||
migrationsdir = os.path.join(dbdir, 'migrations', load_config.get('DATABASE_ENGINE'))
|
||||
if not os.path.isdir(migrationsdir):
|
||||
migrationsdir = os.path.join(dbdir, 'migrations', 'default')
|
||||
|
||||
session = SessionBase.create_session()
|
||||
|
||||
ac = AlembicConfig(os.path.join(migrationsdir, 'alembic.ini'))
|
||||
ac.set_main_option('sqlalchemy.url', database_engine)
|
||||
ac.set_main_option('script_location', migrationsdir)
|
||||
|
||||
alembic.command.downgrade(ac, 'base')
|
||||
alembic.command.upgrade(ac, 'head')
|
||||
|
||||
yield session
|
||||
session.commit()
|
||||
session.close()
|
||||
|
||||
|
16
apps/cic-notify/tests/helpers/phone.py
Normal file
16
apps/cic-notify/tests/helpers/phone.py
Normal file
@ -0,0 +1,16 @@
|
||||
# standard imports
|
||||
|
||||
# external imports
|
||||
from faker import Faker
|
||||
from faker_e164.providers import E164Provider
|
||||
|
||||
# local imports
|
||||
|
||||
# test imports
|
||||
|
||||
fake = Faker()
|
||||
fake.add_provider(E164Provider)
|
||||
|
||||
|
||||
def phone_number() -> str:
|
||||
return fake.e164('KE')
|
@ -1,34 +0,0 @@
|
||||
# standard imports
|
||||
import json
|
||||
|
||||
# third party imports
|
||||
import pytest
|
||||
import celery
|
||||
|
||||
# local imports
|
||||
from cic_notify.tasks.sms import db
|
||||
from cic_notify.tasks.sms import log
|
||||
|
||||
def test_log_notification(
|
||||
celery_session_worker,
|
||||
):
|
||||
|
||||
recipient = '+25412121212'
|
||||
content = 'bar'
|
||||
s_log = celery.signature('cic_notify.tasks.sms.log.log')
|
||||
t = s_log.apply_async(args=[recipient, content])
|
||||
|
||||
r = t.get()
|
||||
|
||||
|
||||
def test_db_notification(
|
||||
init_database,
|
||||
celery_session_worker,
|
||||
):
|
||||
|
||||
recipient = '+25412121213'
|
||||
content = 'foo'
|
||||
s_db = celery.signature('cic_notify.tasks.sms.db.persist_notification')
|
||||
t = s_db.apply_async(args=[recipient, content])
|
||||
|
||||
r = t.get()
|
@ -4,3 +4,4 @@ omit =
|
||||
scripts/*
|
||||
cic_ussd/db/migrations/*
|
||||
cic_ussd/runnable/*
|
||||
cic_ussd/version.py
|
@ -14,7 +14,7 @@ from cic_ussd.account.chain import Chain
|
||||
from cic_ussd.cache import cache_data, cache_data_key, get_cached_data
|
||||
from cic_ussd.error import CachedDataNotFoundError, SeppukuError
|
||||
from cic_ussd.metadata.tokens import query_token_info, query_token_metadata
|
||||
from cic_ussd.processor.util import wait_for_cache
|
||||
from cic_ussd.processor.poller import wait_for_cache
|
||||
|
||||
logg = logging.getLogger(__file__)
|
||||
|
||||
|
@ -14,7 +14,7 @@ class Cache:
|
||||
store: Redis = None
|
||||
|
||||
|
||||
def cache_data(key: str, data: str):
|
||||
def cache_data(key: str, data: [bytes, float, int, str]):
|
||||
"""
|
||||
:param key:
|
||||
:type key:
|
||||
|
9
apps/cic-ussd/cic_ussd/data/sys/guardians.txt
Normal file
9
apps/cic-ussd/cic_ussd/data/sys/guardians.txt
Normal file
@ -0,0 +1,9 @@
|
||||
+254707628499
|
||||
+254757628885
|
||||
+254757628900
|
||||
+254792048646
|
||||
+254792048228
|
||||
+254792048490
|
||||
+254792048902
|
||||
+254727806655
|
||||
+254790079966
|
19
apps/cic-ussd/cic_ussd/data/sys/helpers.csv
Normal file
19
apps/cic-ussd/cic_ussd/data/sys/helpers.csv
Normal file
@ -0,0 +1,19 @@
|
||||
keys,en,sw,kam,kik,miji,luo,bor
|
||||
female,Female,Mwanamke,Mundumuka,Mutumia,Muche,Dhako,Uwole
|
||||
from,From,Kutoka kwa,Kuma kwa,Kuuma kwa,Ulaako,Kowuok kuom,ira
|
||||
male,Male,Mwanaume,Mundume,Mundurume,Mulume,Dichuo,Dir
|
||||
not_provided,Not provided,Haijawekwa,Inenganitwe,Ndiikiritwo,Kaphana,Okoketi,Kes inkan
|
||||
no_language_list,No language list,Hamna lugha ya kuchagua,Vai luka ya kusakwa,Hatire ruthiomi rwakucagurwo,Kahana luga irio orodeshwa,Onge dhok miyiero,Afaan chaguad injirt
|
||||
no_transaction_history,No transaction history,Hamna ripoti ya matumizi,Vai livoti ya utumii,Hatire riboti ya mahuthira,Kahana repoti ya mahumizi,Onge ripot mar tiyo,Odhuu jalkaban injirt
|
||||
no_tokens_list,No more Sarafu,Hamna sarafu zingine,Vai Sarafu ingi,Hatire Sarafu inge,Kahana Sarafu zaidi,Onge Sarafu moko,Sarafu dibii injirt
|
||||
other,Other,Nyingine,Ingi,Inge,Nyinjine,Moko,Ta dibii
|
||||
received,Received,Ulipokea,Niwakwatie,Niuramukirire ,Hokera,Niyudo,Argat
|
||||
sent,Sent,Ulituma,Niwatumie,Niuratumire,Humwa,Nioro,Ergan
|
||||
to,To,Kwa,Kwa,Hare,Kwa,Ne,Es
|
||||
guardians_list_header,Your PIN guards are:,PIN Walinzi uliowaongeza ni:,PIN Atetheesya ala wongelile ni:,Agiteri a PIN yaku ni:,PIN Aimirizi urioika ni:,PIN Jorit magi gin:,PIN Naam at korkorad:
|
||||
no_guardians_list,No PIN guardians set,Hamna PIN walinzi walioongezwa,Vai atetheesya mongelwa,Hartire agiteri meekeretwo,Kahana aimirizi adzoikwa,Onge jorit moketi,Nam an korkorad injirt
|
||||
error.no_phone_number_provided,No phone number was provided,Nambari ya simu haijawekwa,Namba ya simu inaikiwa,Namba ya thimu ndihianetwo,Kahana namba ya simu idzopewa,Namba mar simu okoketi,Namba simu kees inkaan
|
||||
error.no_matching_account,The number provided is not registered,Nambari uliyoweka haijasajiliwa,Namba ya simu ila wekiya ti mbandikithye,Namba iria wekera ndiandekithetwo,Namba idzopewa kaidzagwe kusajiliwa,Namba mar simu miketo pok ondiki,Namba ka at kekeet sajiil incab
|
||||
error.is_initiator,Phone number cannot be your own,Nambari yafaa kuwa tofauti na yako,Namba ya simu yaile ithiwa itavwanene na yaku,Namba ifatie gukorwo ina utiganu na yaku,Namba yasimu kaidima kukala niyako,Namba onego obed mopogre gimari,Namba simu tete tau mal
|
||||
error.is_existent_guardian,This phone number is already added as a PIN guardian,Nambari hii tayari imeongezwa kama mlinzi wa nambari ya siri,Namba ii niyongeletwe tayari ta mutethesya wa kusovya pin,Namba ino niyongereirwo ta murugamereri ya namba ya thiri,Nambari ii yasimu yaikwa kare Muimirizi,Nambani oseketi kaka jarit,Namba tana yayu nam korkoradi taat
|
||||
error.is_not_existent_guardian,Phone number not set as PIN guardian,Nambari hii haijaongezwa kama mlinzi wa nambari ya PIN,Namba ii iyongeletwe ta mutethesya wa kusovya PIN,Namba ino ndiongereirwo ta mugiteri wa PIN,Nambari ii yasimu kaiikika kugaluza PIN zda mwimirizi,Nambani pok omed kaka jarit,Namba simu ta nam korkorad indharan
|
|
7
apps/cic-ussd/cic_ussd/data/sys/sms.csv
Normal file
7
apps/cic-ussd/cic_ussd/data/sys/sms.csv
Normal file
@ -0,0 +1,7 @@
|
||||
keys,en,sw,kam,kik,miji,luo,bor
|
||||
account_successfully_created,You have been registered on Sarafu Network! To use dial *384*96# on Safaricom and *483*96# on other networks. For help %{support_phone},Umesajiliwa kwa Sarafu Network! Kutumia bonyeza *384*96# Safaricom ama *483*46# kwa utandao tofauti. Kwa Usaidizi %{support_phone},Niwayandikithya na Sarafu Network! Safaricom kuna namba ii *384*96# mitandao ingi *483*96#. Utethyo ungi kuna %{support_phone},Niweyandekithia kwe Sarafu Network! Kuhuthira hihinya *384*96# he Safaricom na *483*46# he mitambo ingi Uteithio %{support_phone},Usajiliwa Sarafu Network! kuhumira hopya *384*96# Saf *483*96# mtandao mnjine. Kuvizwa %{support_phone},Osendiki e Sarafu Network! Kidwatiyogo to dii *384*96# Safaricom kata *483*46# e netwak mamoko. Kuom kony %{support_phone},Yaayu sirejestan Sarafu Network! Kuches *384*96# Safaricom *483*46# Airtel
|
||||
received_tokens,Successfully received %{amount} %{token_symbol} from %{tx_sender_information} %{timestamp} to %{tx_recipient_information} Balance %{balance} %{token_symbol},Umepokea %{amount} %{token_symbol} kutoka kwa %{tx_sender_information} %{timestamp} kuendea %{tx_recipient_information} Salio %{balance} %{token_symbol},Niwakwata %{amount} %{token_symbol} kuma %{tx_sender_information} %{timestamp} kuvikia %{tx_recipient_information} Mbalansi %{balance} %{token_symbol},Wamukira %{amount} %{token_symbol} kuuma kwa %{tx_sender_information} %{timestamp} to %{tx_recipient_information} Watigaria %{balance} %{token_symbol},Uphokera %{amount} %{token_symbol} kula %{tx_sender_information} %{timestamp} Kwenda %{tx_recipient_information}. Sazoro %{balance} %{token_symbol},Iyudo %{amount} %{token_symbol} kowuok kuom %{tx_sender_information} %{timestamp} odhi ne %{tx_recipient_information}. Dong mari en %{balance} %{token_symbol},Yaargat %{amount} %{token_symbol} ira %{tx_sender_information} %{timestamp} Es %{tx_recipient_information} Balansi %{balance} %{token_symbol}
|
||||
sent_tokens,Successfully sent %{amount} %{token_symbol} to %{tx_recipient_information} %{timestamp} from %{tx_sender_information} Balance %{balance} %{token_symbol},Umetuma %{amount} %{token_symbol} kwa %{tx_recipient_information} %{timestamp} kutoka kwa %{tx_sender_information} Salio %{balance} %{token_symbol},Niwatuma %{amount} %{token_symbol} kwa %{tx_recipient_information} %{timestamp} kuma %{tx_sender_information} Mbalansi %{balance} %{token_symbol}.,Watuma %{amount} %{token_symbol} kwe %{tx_recipient_information} %{timestamp} kuuma %{tx_sender_information} Watigaria %{balance} %{token_symbol},Uhuma %{amount} %{token_symbol} kwa %{tx_recipient_information} %{timestamp} kula %{tx_sender_information} Sazoro %{balance} %{token_symbol},Ioro %{amount} %{token_symbol} ne %{tx_recipient_information} %{timestamp} kowuok kuom %{tx_sender_information}. Dong mari en %{balance} %{token_symbol},yaergat %{amount} %{token_symbol} Es %{tx_recipient_information} %{timestamp} ira %{tx_sender_information} Balansi hareetin %{balance} %{token_symbol}
|
||||
terms,By using the service you agree to the terms and conditions at http://grassecon.org/tos,Kwa kutumia hii huduma umekubali sheria na masharti yafuatayo http://grassecon.org/tos,Kwa kutumia mutandao uu niwetikilana na miyao na masharti ma http://grassecon.org/tos,"Kuhuthira mitambo ino , niuraetekania na mawatho na mutaratara wa http://grassecon.org/tos","Kuhumira huduma,Ukubali sheria na malagizo http://grassecon.org/tos ",Kuom tiyo gi huduma ni iyie chike kod weche mantie http://grassecon.org/tos,Oja service tun tumiith yaayuu kubalt one chuf at http://grassecon.org/tos
|
||||
upsell_unregistered_recipient,%{tx_sender_information} tried to send you %{token_symbol}. Dial *384*96# on Safaricom and *483*96# on others For help %{support_phone},%{tx_sender_information} amejaribu kutuma %{token_symbol} na hujasajili. Bonyeza*384*96# Saf au*483*46# kwa mitandao tofauti. Usaidizi %{support_phone},%{tx_sender_information} niwatata kuutumia %{token_symbol} lakini ndwimwandikithye. Safaricom kuna *384*96# laini ingi *483*96# Utethyo %{support_phone},%{tx_sender_information} ekugeretie gugutumira %{token_symbol} no ndeyandikithetie. Hihinya *384*96# he Safaricom na *483*96# mitambo ingi. Uteithio %{support_phone},%{tx_sender_information} Yuhuma %{token_symbol} Kudzasajiliwa. Humira hopya *384*96# Safaricom au *483*96# mtandao mnjine. Kuvizwa %{support_phone},%{tx_sender_information} otemo oro ni %{token_symbol} to pok ondiki. Tiyo go dii *384*96# Safaricom gi *483*96# e netwak mamoko. E kony %{support_phone},%{tx_sender_information} yaa si ergu jariib %{token_symbol} ammo atin insajilan.Tumiitu kuches *384*96# Safaricom *483*96# dibii Qarqars %{support_phone}
|
||||
pin_reset_initiated,%{pin_initiator} has sent a request to initiate your PIN reset,%{pin_initiator} ametuma ombi la kubadilisha PIN yako,%{pin_initiator} niwatuma wendi waku wa kwambiisya kusovya PIN yaku,%{pin_initiator} Niatuma ihoya ria guchengia PIN yaku,%{pin_initiator} yuhuma voyo kurekebisha piniyo.,%{pin_initiator} ooro kwayo mar loko nambani mopondo,%{pin_initiator} pin Tate badilishadu feet
|
|
1098
apps/cic-ussd/cic_ussd/data/sys/ussd.csv
Normal file
1098
apps/cic-ussd/cic_ussd/data/sys/ussd.csv
Normal file
File diff suppressed because it is too large
Load Diff
@ -63,10 +63,7 @@ class Account(SessionBase):
|
||||
def remove_guardian(self, phone_number: str):
|
||||
set_guardians = self.guardians.split(',')
|
||||
set_guardians.remove(phone_number)
|
||||
if len(set_guardians) > 1:
|
||||
self.guardians = ','.join(set_guardians)
|
||||
else:
|
||||
self.guardians = set_guardians[0]
|
||||
|
||||
def get_guardians(self) -> list:
|
||||
return self.guardians.split(',') if self.guardians else []
|
||||
|
@ -52,4 +52,5 @@ class UnknownUssdRecipient(Exception):
|
||||
"""Raised when a recipient of a transaction is not known to the ussd application."""
|
||||
|
||||
|
||||
|
||||
class MaxRetryReached(Exception):
|
||||
"""Raised when the maximum number of retries defined for polling for the availability of a resource."""
|
||||
|
@ -7,3 +7,4 @@ from .custom import CustomMetadata
|
||||
from .person import PersonMetadata
|
||||
from .phone import PhonePointerMetadata
|
||||
from .preferences import PreferencesMetadata
|
||||
from .tokens import TokenMetadata
|
||||
|
@ -31,7 +31,8 @@ from cic_ussd.cache import cache_data_key, cache_data, get_cached_data
|
||||
from cic_ussd.db.models.account import Account
|
||||
from cic_ussd.metadata import PersonMetadata
|
||||
from cic_ussd.phone_number import Support
|
||||
from cic_ussd.processor.util import parse_person_metadata, ussd_menu_list, wait_for_session_data
|
||||
from cic_ussd.processor.poller import wait_for_session_data
|
||||
from cic_ussd.processor.util import parse_person_metadata, ussd_menu_list
|
||||
from cic_ussd.session.ussd_session import save_session_data
|
||||
from cic_ussd.state_machine.logic.language import preferred_langauge_from_selection
|
||||
from cic_ussd.translation import translation_for
|
||||
@ -417,7 +418,7 @@ class MenuProcessor:
|
||||
preferred_language = get_cached_preferred_language(self.account.blockchain_address)
|
||||
if not preferred_language:
|
||||
preferred_language = i18n.config.get('fallback')
|
||||
return translation_for(self.display_key,preferred_language,token_symbol=token_symbol)
|
||||
return translation_for(self.display_key, preferred_language, token_symbol=token_symbol)
|
||||
|
||||
def exit_successful_transaction(self):
|
||||
"""
|
||||
|
104
apps/cic-ussd/cic_ussd/processor/poller.py
Normal file
104
apps/cic-ussd/cic_ussd/processor/poller.py
Normal file
@ -0,0 +1,104 @@
|
||||
# standard imports
|
||||
import logging
|
||||
import time
|
||||
from queue import Queue
|
||||
from typing import Callable, Dict, Optional, Tuple, Union
|
||||
|
||||
# external imports
|
||||
from cic_types.condiments import MetadataPointer
|
||||
|
||||
# local imports
|
||||
from cic_ussd.cache import cache_data_key, get_cached_data
|
||||
from cic_ussd.error import MaxRetryReached
|
||||
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
# adapted from https://github.com/justiniso/polling/blob/master/polling.py
|
||||
# opted not to use the package to reduce dependency
|
||||
def poller(args: Optional[Tuple],
|
||||
interval: int,
|
||||
kwargs: Optional[Dict],
|
||||
max_retry: int,
|
||||
target: Callable[..., Union[Dict, str]]):
|
||||
""""""
|
||||
collected_values: list = []
|
||||
expected_value = None
|
||||
tries = 0
|
||||
|
||||
while True:
|
||||
if tries >= max_retry:
|
||||
raise MaxRetryReached(collected_values, expected_value)
|
||||
try:
|
||||
if args:
|
||||
value = target(*args)
|
||||
elif kwargs:
|
||||
value = target(**kwargs)
|
||||
else:
|
||||
value = target()
|
||||
expected_value = value
|
||||
except () as error:
|
||||
expected_value = error
|
||||
else:
|
||||
if bool(value) or value == {}:
|
||||
logg.debug(f'Resource: {expected_value} now available.')
|
||||
break
|
||||
collected_values.append(expected_value)
|
||||
logg.debug(f'Collected values are: {collected_values}')
|
||||
tries += 1
|
||||
time.sleep(interval)
|
||||
|
||||
|
||||
def wait_for_cache(identifier: Union[list, bytes],
|
||||
resource_name: str,
|
||||
salt: MetadataPointer,
|
||||
interval: int = 1,
|
||||
max_retry: int = 5):
|
||||
"""
|
||||
:param identifier:
|
||||
:type identifier:
|
||||
:param interval:
|
||||
:type interval:
|
||||
:param resource_name:
|
||||
:type resource_name:
|
||||
:param salt:
|
||||
:type salt:
|
||||
:param max_retry:
|
||||
:type max_retry:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
key: str = cache_data_key(identifier=identifier, salt=salt)
|
||||
logg.debug(f'Polling for resource: {resource_name} at: {key} every: {interval} second(s) for {max_retry} seconds.')
|
||||
poller(args=(key,), interval=interval, kwargs=None, max_retry=max_retry, target=get_cached_data)
|
||||
|
||||
|
||||
def wait_for_session_data(resource_name: str,
|
||||
session_data_key: str,
|
||||
ussd_session: dict,
|
||||
interval: int = 1,
|
||||
max_retry: int = 5):
|
||||
"""
|
||||
:param interval:
|
||||
:type interval:
|
||||
:param resource_name:
|
||||
:type resource_name:
|
||||
:param session_data_key:
|
||||
:type session_data_key:
|
||||
:param ussd_session:
|
||||
:type ussd_session:
|
||||
:param max_retry:
|
||||
:type max_retry:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
# poll for data element first
|
||||
logg.debug(f'Data poller with max retry at: {max_retry}. Checking for every: {interval} seconds.')
|
||||
poller(args=('data',), interval=interval, kwargs=None, max_retry=max_retry, target=ussd_session.get)
|
||||
|
||||
# poll for session data element
|
||||
get_session_data = ussd_session.get('data').get
|
||||
logg.debug(f'Session data poller for: {resource_name} with max retry at: {max_retry}. Checking for every: {interval} seconds.')
|
||||
poller(args=(session_data_key,), interval=interval, kwargs=None, max_retry=max_retry, target=get_session_data)
|
||||
|
@ -102,77 +102,3 @@ def ussd_menu_list(fallback: str, menu_list: list, split: int = 3) -> List[str]:
|
||||
except IndexError:
|
||||
menu_list_reprs.append(fallback)
|
||||
return menu_list_reprs
|
||||
|
||||
|
||||
def wait_for_cache(identifier: Union[list, bytes], resource_name: str, salt: MetadataPointer, interval: int = 1, max_retry: int = 5):
|
||||
"""
|
||||
:param identifier:
|
||||
:type identifier:
|
||||
:param interval:
|
||||
:type interval:
|
||||
:param resource_name:
|
||||
:type resource_name:
|
||||
:param salt:
|
||||
:type salt:
|
||||
:param max_retry:
|
||||
:type max_retry:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
key = cache_data_key(identifier=identifier, salt=salt)
|
||||
resource = get_cached_data(key)
|
||||
counter = 0
|
||||
while resource is None:
|
||||
logg.debug(f'Waiting for: {resource_name} at: {key}. Checking after: {interval} ...')
|
||||
time.sleep(interval)
|
||||
counter += 1
|
||||
resource = get_cached_data(key)
|
||||
if resource is not None:
|
||||
logg.debug(f'{resource_name} now available.')
|
||||
break
|
||||
else:
|
||||
if counter == max_retry:
|
||||
logg.debug(f'Could not find: {resource_name} within: {max_retry}')
|
||||
break
|
||||
|
||||
|
||||
def wait_for_session_data(resource_name: str, session_data_key: str, ussd_session: dict, interval: int = 1, max_retry: int = 5):
|
||||
"""
|
||||
:param interval:
|
||||
:type interval:
|
||||
:param resource_name:
|
||||
:type resource_name:
|
||||
:param session_data_key:
|
||||
:type session_data_key:
|
||||
:param ussd_session:
|
||||
:type ussd_session:
|
||||
:param max_retry:
|
||||
:type max_retry:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
data = ussd_session.get('data')
|
||||
data_poller = 0
|
||||
while not data:
|
||||
logg.debug(f'Waiting for data object on ussd session: {ussd_session.get("external_session_id")}')
|
||||
logg.debug(f'Data poller at: {data_poller}. Checking again after: {interval} secs...')
|
||||
time.sleep(interval)
|
||||
data_poller += 1
|
||||
if data:
|
||||
logg.debug(f'Data object found, proceeding to poll for: {session_data_key}')
|
||||
break
|
||||
if data:
|
||||
session_data_poller = 0
|
||||
session_data = data.get(session_data_key)
|
||||
while not session_data_key:
|
||||
logg.debug(
|
||||
f'Session data poller at: {data_poller} with max retry at: {max_retry}. Checking again after: {interval} secs...')
|
||||
time.sleep(interval)
|
||||
session_data_poller += 1
|
||||
|
||||
if session_data:
|
||||
logg.debug(f'{resource_name} now available.')
|
||||
break
|
||||
|
||||
elif session_data_poller >= max_retry:
|
||||
logg.debug(f'Could not find data object within: {max_retry}')
|
||||
|
@ -11,7 +11,7 @@ from sqlalchemy.orm.session import Session
|
||||
# local imports
|
||||
from cic_ussd.cache import cache_data_key, get_cached_data
|
||||
from cic_ussd.db.models.account import Account
|
||||
from cic_ussd.processor.util import wait_for_cache, wait_for_session_data
|
||||
from cic_ussd.processor.poller import wait_for_cache, wait_for_session_data
|
||||
from cic_ussd.session.ussd_session import save_session_data
|
||||
from cic_ussd.translation import Languages
|
||||
|
||||
|
@ -15,7 +15,7 @@ from cic_ussd.db.models.account import Account
|
||||
from cic_ussd.db.models.base import SessionBase
|
||||
from cic_ussd.db.enum import AccountStatus
|
||||
from cic_ussd.encoder import create_password_hash, check_password_hash
|
||||
from cic_ussd.processor.util import wait_for_session_data
|
||||
from cic_ussd.processor.poller import wait_for_session_data
|
||||
from cic_ussd.session.ussd_session import create_or_update_session, persist_ussd_session
|
||||
|
||||
|
||||
|
@ -87,7 +87,7 @@ def is_valid_guardian_addition(state_machine_data: Tuple[str, dict, Account, Ses
|
||||
guardianship = Guardianship()
|
||||
is_system_guardian = guardianship.is_system_guardian(phone_number)
|
||||
is_initiator = phone_number == account.phone_number
|
||||
is_existent_guardian = phone_number in account.get_guardians()
|
||||
is_existent_guardian = phone_number in account.get_guardians() or is_system_guardian
|
||||
|
||||
failure_reason = ''
|
||||
if not is_valid_account:
|
||||
|
@ -7,7 +7,7 @@ from sqlalchemy.orm.session import Session
|
||||
# local imports
|
||||
from cic_ussd.account.tokens import set_active_token
|
||||
from cic_ussd.db.models.account import Account
|
||||
from cic_ussd.processor.util import wait_for_session_data
|
||||
from cic_ussd.processor.poller import wait_for_session_data
|
||||
from cic_ussd.session.ussd_session import save_session_data
|
||||
|
||||
|
||||
|
@ -15,7 +15,7 @@ from cic_ussd.cache import Cache, cache_data, cache_data_key, get_cached_data
|
||||
from cic_ussd.account.chain import Chain
|
||||
from cic_ussd.db.models.base import SessionBase
|
||||
from cic_ussd.db.models.account import Account
|
||||
from cic_ussd.processor.util import wait_for_cache
|
||||
from cic_ussd.processor.poller import wait_for_cache
|
||||
from cic_ussd.account.statement import filter_statement_transactions
|
||||
from cic_ussd.account.transaction import transaction_actors
|
||||
from cic_ussd.account.tokens import (collate_token_metadata,
|
||||
|
@ -13,4 +13,4 @@ port =
|
||||
ssl =
|
||||
|
||||
[system]
|
||||
guardians_file = var/lib/sys/guardians.txt
|
||||
guardians_file = cic_ussd/data/sys/guardians.txt
|
||||
|
@ -6,3 +6,6 @@ password_pepper=QYbzKff6NhiQzY3ygl2BkiKOpER8RE/Upqs/5aZWW+I=
|
||||
[machine]
|
||||
states=states/
|
||||
transitions=transitions/
|
||||
|
||||
[system]
|
||||
guardians_file = cic_ussd/data/sys/guardians.txt
|
||||
|
@ -1,2 +1,2 @@
|
||||
[chain]
|
||||
spec = 'evm:foo:1:bar'
|
||||
spec = evm:foo:1:bar
|
||||
|
@ -1,3 +1,10 @@
|
||||
[locale]
|
||||
fallback=sw
|
||||
path=var/lib/locale/
|
||||
path=cic_ussd/data/locale/
|
||||
file_builders=cic_ussd/data/sys/
|
||||
|
||||
[schema]
|
||||
file_path = data/schema
|
||||
|
||||
[languages]
|
||||
file = cic_ussd/data/sys/languages.json
|
||||
|
@ -1,5 +1,5 @@
|
||||
[ussd]
|
||||
menu_file=cic_ussd/db/ussd_menu.json
|
||||
menu_file=cic_ussd/data/sys/ussd_menu.json
|
||||
service_code=*483*46#,*483*061#,*384*96#
|
||||
user =
|
||||
pass =
|
||||
|
@ -1,10 +1,10 @@
|
||||
[locale]
|
||||
fallback=sw
|
||||
path=var/lib/locale/
|
||||
file_builders=var/lib/sys/
|
||||
path=cic_ussd/data/locale/
|
||||
file_builders=cic_ussd/data/sys/
|
||||
|
||||
[schema]
|
||||
file_path = /usr/local/lib/python3.8/site-packages/cic_translations/data/schema
|
||||
|
||||
[languages]
|
||||
file = var/lib/sys/languages.json
|
||||
file = cic_ussd/data/sys/languages.json
|
||||
|
@ -1,5 +1,5 @@
|
||||
[ussd]
|
||||
menu_file=data/ussd_menu.json
|
||||
menu_file=cic_ussd/data/sys/ussd_menu.json
|
||||
service_code=*483*46#,*483*061#,*384*96#
|
||||
user =
|
||||
pass =
|
||||
|
@ -3,12 +3,10 @@ ARG DOCKER_REGISTRY="registry.gitlab.com/grassrootseconomics"
|
||||
FROM $DOCKER_REGISTRY/cic-base-images:python-3.8.6-dev-e8eb2ee2
|
||||
|
||||
RUN apt-get install -y redis-server
|
||||
|
||||
# create secrets directory
|
||||
RUN mkdir -vp pgp/keys
|
||||
|
||||
# create application directory
|
||||
RUN mkdir -vp cic-ussd
|
||||
RUN mkdir -vp data
|
||||
|
||||
ARG EXTRA_PIP_INDEX_URL=https://pip.grassrootseconomics.net
|
||||
ARG EXTRA_PIP_ARGS=""
|
||||
@ -25,7 +23,8 @@ RUN --mount=type=cache,mode=0755,target=/root/.cache/pip \
|
||||
COPY . .
|
||||
RUN python setup.py install
|
||||
|
||||
COPY cic_ussd/db/ussd_menu.json data/
|
||||
# create local files directory
|
||||
RUN mkdir -vp cic_ussd/data/locale
|
||||
|
||||
COPY docker/*.sh ./
|
||||
RUN chmod +x /root/*.sh
|
||||
|
@ -1,12 +1,12 @@
|
||||
cic-eth[services]~=0.12.4a13
|
||||
Faker==8.1.2
|
||||
cic-eth[services]~=0.12.7
|
||||
Faker==11.1.0
|
||||
faker-e164==0.1.0
|
||||
pytest==6.2.4
|
||||
pytest-alembic==0.2.5
|
||||
pytest==6.2.5
|
||||
pytest-alembic==0.7.0
|
||||
pytest-celery==0.0.0a1
|
||||
pytest-cov==2.10.1
|
||||
pytest-mock==3.3.1
|
||||
pytest-cov==3.0.0
|
||||
pytest-mock==3.6.1
|
||||
pytest-ordering==0.6
|
||||
pytest-redis==2.0.0
|
||||
requests-mock==1.8.0
|
||||
tavern==1.14.2
|
||||
pytest-redis==2.3.0
|
||||
requests-mock==1.9.3
|
||||
tavern==1.18.0
|
||||
|
@ -2,10 +2,16 @@
|
||||
|
||||
# external imports
|
||||
import pytest
|
||||
from cic_types.condiments import MetadataPointer
|
||||
|
||||
# local imports
|
||||
from cic_ussd.account.balance import calculate_available_balance, get_balances, get_cached_available_balance
|
||||
from cic_ussd.account.balance import (calculate_available_balance,
|
||||
get_balances,
|
||||
get_cached_adjusted_balance,
|
||||
get_cached_available_balance)
|
||||
from cic_ussd.account.chain import Chain
|
||||
from cic_ussd.account.tokens import get_cached_token_data_list
|
||||
from cic_ussd.cache import cache_data_key, get_cached_data
|
||||
from cic_ussd.error import CachedDataNotFoundError
|
||||
|
||||
# test imports
|
||||
@ -57,19 +63,45 @@ def test_calculate_available_balance(activated_account,
|
||||
'balance_outgoing': balance_outgoing,
|
||||
'balance_incoming': balance_incoming
|
||||
}
|
||||
assert calculate_available_balance(balances) == available_balance
|
||||
assert calculate_available_balance(balances, 6) == available_balance
|
||||
|
||||
|
||||
def test_get_cached_available_balance(activated_account,
|
||||
balances,
|
||||
cache_balances,
|
||||
cache_default_token_data,
|
||||
load_chain_spec):
|
||||
cached_available_balance = get_cached_available_balance(activated_account.blockchain_address)
|
||||
available_balance = calculate_available_balance(balances[0])
|
||||
load_chain_spec,
|
||||
token_symbol):
|
||||
identifier = [bytes.fromhex(activated_account.blockchain_address), token_symbol.encode('utf-8')]
|
||||
cached_available_balance = get_cached_available_balance(6, identifier)
|
||||
available_balance = calculate_available_balance(balances[0], 6)
|
||||
assert cached_available_balance == available_balance
|
||||
address = blockchain_address()
|
||||
with pytest.raises(CachedDataNotFoundError) as error:
|
||||
cached_available_balance = get_cached_available_balance(address)
|
||||
identifier = [bytes.fromhex(address), token_symbol.encode('utf-8')]
|
||||
key = cache_data_key(identifier=identifier, salt=MetadataPointer.BALANCES)
|
||||
cached_available_balance = get_cached_available_balance(6, identifier)
|
||||
assert cached_available_balance is None
|
||||
assert str(error.value) == f'No cached available balance for address: {address}'
|
||||
assert str(error.value) == f'No cached available balance at {key}'
|
||||
|
||||
|
||||
def test_get_cached_adjusted_balance(activated_account, cache_adjusted_balances, token_symbol):
|
||||
identifier = bytes.fromhex(activated_account.blockchain_address)
|
||||
balances_identifier = [identifier, token_symbol.encode('utf-8')]
|
||||
key = cache_data_key(balances_identifier, MetadataPointer.BALANCES_ADJUSTED)
|
||||
adjusted_balances = get_cached_data(key)
|
||||
assert get_cached_adjusted_balance(balances_identifier) == adjusted_balances
|
||||
|
||||
|
||||
def test_get_account_tokens_balance(activated_account,
|
||||
cache_token_data_list,
|
||||
celery_session_worker,
|
||||
load_chain_spec,
|
||||
load_config,
|
||||
mock_async_balance_api_query,
|
||||
token_symbol):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
chain_str = Chain.spec.__str__()
|
||||
get_balances(blockchain_address, chain_str, token_symbol, asynchronous=True)
|
||||
assert mock_async_balance_api_query.get('address') == blockchain_address
|
||||
assert mock_async_balance_api_query.get('token_symbol') == token_symbol
|
||||
|
21
apps/cic-ussd/tests/cic_ussd/account/test_guardianship.py
Normal file
21
apps/cic-ussd/tests/cic_ussd/account/test_guardianship.py
Normal file
@ -0,0 +1,21 @@
|
||||
# standard imports
|
||||
import os
|
||||
|
||||
# external imports
|
||||
|
||||
# local imports
|
||||
from cic_ussd.account.guardianship import Guardianship
|
||||
|
||||
# test imports
|
||||
from tests.fixtures.config import root_directory
|
||||
|
||||
|
||||
def test_guardianship(load_config, setup_guardianship):
|
||||
guardians_file = os.path.join(root_directory, load_config.get('SYSTEM_GUARDIANS_FILE'))
|
||||
with open(guardians_file, 'r') as system_guardians:
|
||||
guardians = [line.strip() for line in system_guardians]
|
||||
assert Guardianship.guardians == guardians
|
||||
|
||||
guardianship = Guardianship()
|
||||
assert guardianship.is_system_guardian(Guardianship.guardians[0]) is True
|
||||
assert guardianship.is_system_guardian('+254712345678') is False
|
@ -11,8 +11,7 @@ from cic_ussd.account.statement import (filter_statement_transactions,
|
||||
generate,
|
||||
get_cached_statement,
|
||||
parse_statement_transactions,
|
||||
query_statement,
|
||||
statement_transaction_set)
|
||||
query_statement)
|
||||
from cic_ussd.account.transaction import transaction_actors
|
||||
from cic_ussd.cache import cache_data_key, get_cached_data
|
||||
|
||||
@ -74,12 +73,3 @@ def test_query_statement(blockchain_address, limit, load_chain_spec, activated_a
|
||||
query_statement(blockchain_address, limit)
|
||||
assert mock_transaction_list_query.get('address') == blockchain_address
|
||||
assert mock_transaction_list_query.get('limit') == limit
|
||||
|
||||
|
||||
def test_statement_transaction_set(cache_default_token_data, load_chain_spec, preferences, set_locale_files, statement):
|
||||
parsed_transactions = parse_statement_transactions(statement)
|
||||
preferred_language = preferences.get('preferred_language')
|
||||
transaction_set = statement_transaction_set(preferred_language, parsed_transactions)
|
||||
transaction_set.startswith('Sent')
|
||||
transaction_set = statement_transaction_set(preferred_language, [])
|
||||
transaction_set.startswith('No')
|
||||
|
@ -1,17 +1,80 @@
|
||||
# standard imports
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
# external imports
|
||||
import pytest
|
||||
from cic_types.condiments import MetadataPointer
|
||||
|
||||
# local imports
|
||||
from cic_ussd.account.chain import Chain
|
||||
from cic_ussd.account.tokens import get_cached_default_token, get_default_token_symbol, query_default_token
|
||||
from cic_ussd.account.tokens import (collate_token_metadata,
|
||||
create_account_tokens_list,
|
||||
get_active_token_symbol,
|
||||
get_default_token_symbol,
|
||||
get_cached_default_token,
|
||||
get_cached_token_data,
|
||||
get_cached_token_data_list,
|
||||
get_cached_token_symbol_list,
|
||||
hashed_token_proof,
|
||||
handle_token_symbol_list,
|
||||
order_account_tokens_list,
|
||||
parse_token_list,
|
||||
process_token_data,
|
||||
query_default_token,
|
||||
query_token_data,
|
||||
remove_from_account_tokens_list,
|
||||
set_active_token)
|
||||
from cic_ussd.cache import cache_data, cache_data_key, get_cached_data
|
||||
from cic_ussd.error import CachedDataNotFoundError
|
||||
|
||||
|
||||
# test imports
|
||||
|
||||
|
||||
def test_collate_token_metadata(token_meta_symbol, token_proof_symbol):
|
||||
description = token_proof_symbol.get('description')
|
||||
issuer = token_proof_symbol.get('issuer')
|
||||
location = token_meta_symbol.get('location')
|
||||
contact = token_meta_symbol.get('contact')
|
||||
data = {
|
||||
'description': description,
|
||||
'issuer': issuer,
|
||||
'location': location,
|
||||
'contact': contact
|
||||
}
|
||||
assert collate_token_metadata(token_proof_symbol, token_meta_symbol) == data
|
||||
|
||||
|
||||
def test_create_account_tokens_list(activated_account,
|
||||
cache_balances,
|
||||
cache_token_data,
|
||||
cache_token_symbol_list,
|
||||
init_cache):
|
||||
create_account_tokens_list(activated_account.blockchain_address)
|
||||
key = cache_data_key(bytes.fromhex(activated_account.blockchain_address), MetadataPointer.TOKEN_DATA_LIST)
|
||||
cached_data_list = json.loads(get_cached_data(key))
|
||||
data = get_cached_token_data_list(activated_account.blockchain_address)
|
||||
assert cached_data_list == data
|
||||
|
||||
|
||||
def test_get_active_token_symbol(activated_account, set_active_token, valid_recipient):
|
||||
identifier = bytes.fromhex(activated_account.blockchain_address)
|
||||
key = cache_data_key(identifier=identifier, salt=MetadataPointer.TOKEN_ACTIVE)
|
||||
active_token_symbol = get_cached_data(key)
|
||||
assert active_token_symbol == get_active_token_symbol(activated_account.blockchain_address)
|
||||
with pytest.raises(CachedDataNotFoundError) as error:
|
||||
get_active_token_symbol(valid_recipient.blockchain_address)
|
||||
assert str(error.value) == 'No active token set.'
|
||||
|
||||
|
||||
def test_get_cached_token_data(activated_account, cache_token_data, token_symbol):
|
||||
identifier = [bytes.fromhex(activated_account.blockchain_address), token_symbol.encode('utf-8')]
|
||||
key = cache_data_key(identifier, MetadataPointer.TOKEN_DATA)
|
||||
token_data = json.loads(get_cached_data(key))
|
||||
assert token_data == get_cached_token_data(activated_account.blockchain_address, token_symbol)
|
||||
|
||||
|
||||
def test_get_cached_default_token(cache_default_token_data, default_token_data, load_chain_spec):
|
||||
chain_str = Chain.spec.__str__()
|
||||
cached_default_token = get_cached_default_token(chain_str)
|
||||
@ -27,6 +90,84 @@ def test_get_default_token_symbol_from_api(default_token_data, load_chain_spec,
|
||||
assert default_token_symbol == default_token_data['symbol']
|
||||
|
||||
|
||||
def test_get_cached_token_data_list(activated_account, cache_token_data_list):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
key = cache_data_key(identifier=bytes.fromhex(blockchain_address), salt=MetadataPointer.TOKEN_DATA_LIST)
|
||||
token_symbols_list = json.loads(get_cached_data(key))
|
||||
assert token_symbols_list == get_cached_token_data_list(blockchain_address)
|
||||
|
||||
|
||||
def test_get_cached_token_symbol_list(activated_account, cache_token_symbol_list):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
key = cache_data_key(identifier=bytes.fromhex(blockchain_address), salt=MetadataPointer.TOKEN_SYMBOLS_LIST)
|
||||
token_symbols_list = json.loads(get_cached_data(key))
|
||||
assert token_symbols_list == get_cached_token_symbol_list(blockchain_address)
|
||||
|
||||
|
||||
def test_hashed_token_proof(token_proof_symbol):
|
||||
hash_object = hashlib.new("sha256")
|
||||
token_proof = json.dumps(token_proof_symbol)
|
||||
hash_object.update(token_proof.encode('utf-8'))
|
||||
assert hash_object.digest().hex() == hashed_token_proof(token_proof_symbol)
|
||||
|
||||
|
||||
def test_handle_token_symbol_list(activated_account, init_cache):
|
||||
handle_token_symbol_list(activated_account.blockchain_address, 'GFT')
|
||||
cached_token_symbol_list = get_cached_token_symbol_list(activated_account.blockchain_address)
|
||||
assert len(cached_token_symbol_list) == 1
|
||||
handle_token_symbol_list(activated_account.blockchain_address, 'DET')
|
||||
cached_token_symbol_list = get_cached_token_symbol_list(activated_account.blockchain_address)
|
||||
assert len(cached_token_symbol_list) == 2
|
||||
|
||||
|
||||
def test_order_account_tokens_list(activated_account, token_list_entries):
|
||||
identifier = bytes.fromhex(activated_account.blockchain_address)
|
||||
last_sent_token_key = cache_data_key(identifier=identifier, salt=MetadataPointer.TOKEN_LAST_SENT)
|
||||
cache_data(last_sent_token_key, 'FII')
|
||||
|
||||
last_received_token_key = cache_data_key(identifier=identifier, salt=MetadataPointer.TOKEN_LAST_RECEIVED)
|
||||
cache_data(last_received_token_key, 'DET')
|
||||
|
||||
ordered_list = order_account_tokens_list(token_list_entries, identifier)
|
||||
assert ordered_list == [
|
||||
{
|
||||
'name': 'Fee',
|
||||
'symbol': 'FII',
|
||||
'issuer': 'Foo',
|
||||
'contact': {
|
||||
'phone': '+254712345678'
|
||||
},
|
||||
'location': 'Fum',
|
||||
'balance': 50.0
|
||||
},
|
||||
{
|
||||
'name': 'Demurrage Token',
|
||||
'symbol': 'DET',
|
||||
'issuer': 'Grassroots Economics',
|
||||
'contact': {
|
||||
'phone': '+254700000000',
|
||||
'email': 'info@grassrootseconomics.org'},
|
||||
'location': 'Fum',
|
||||
'balance': 49.99
|
||||
},
|
||||
{
|
||||
'name': 'Giftable Token',
|
||||
'symbol': 'GFT',
|
||||
'issuer': 'Grassroots Economics',
|
||||
'contact': {
|
||||
'phone': '+254700000000',
|
||||
'email': 'info@grassrootseconomics.org'},
|
||||
'location': 'Fum',
|
||||
'balance': 60.0
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_parse_token_list(token_list_entries):
|
||||
parsed_token_list = ['1. FII 50.0', '2. GFT 60.0', '3. DET 49.99']
|
||||
assert parsed_token_list == parse_token_list(token_list_entries)
|
||||
|
||||
|
||||
def test_query_default_token(default_token_data, load_chain_spec, mock_sync_default_token_api_query):
|
||||
chain_str = Chain.spec.__str__()
|
||||
queried_default_token_data = query_default_token(chain_str)
|
||||
@ -40,3 +181,38 @@ def test_get_default_token_symbol_from_cache(cache_default_token_data, default_t
|
||||
default_token_symbol = get_default_token_symbol()
|
||||
assert default_token_symbol is not None
|
||||
assert default_token_symbol == default_token_data.get('symbol')
|
||||
|
||||
|
||||
def test_remove_from_account_tokens_list(token_list_entries):
|
||||
assert remove_from_account_tokens_list(token_list_entries, 'GFT') == ([{
|
||||
'name': 'Giftable Token',
|
||||
'symbol': 'GFT',
|
||||
'issuer': 'Grassroots Economics',
|
||||
'contact': {
|
||||
'phone': '+254700000000',
|
||||
'email': 'info@grassrootseconomics.org'
|
||||
},
|
||||
'location': 'Fum',
|
||||
'balance': 60.0
|
||||
}],
|
||||
[
|
||||
{
|
||||
'name': 'Fee',
|
||||
'symbol': 'FII',
|
||||
'issuer': 'Foo',
|
||||
'contact': {'phone': '+254712345678'},
|
||||
'location': 'Fum',
|
||||
'balance': 50.0
|
||||
},
|
||||
{
|
||||
'name': 'Demurrage Token',
|
||||
'symbol': 'DET',
|
||||
'issuer': 'Grassroots Economics',
|
||||
'contact': {
|
||||
'phone': '+254700000000',
|
||||
'email': 'info@grassrootseconomics.org'
|
||||
},
|
||||
'location': 'Fum',
|
||||
'balance': 49.99
|
||||
}
|
||||
])
|
||||
|
@ -1,5 +1,4 @@
|
||||
# standard imports
|
||||
from decimal import Decimal
|
||||
|
||||
# external imports
|
||||
import pytest
|
||||
@ -37,11 +36,11 @@ def test_aux_transaction_data(preferences, set_locale_files, transactions_list):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value, expected_result", [
|
||||
(50000000, Decimal('50.00')),
|
||||
(100000, Decimal('0.10'))
|
||||
(50000000, 50.0),
|
||||
(100000, 0.1)
|
||||
])
|
||||
def test_from_wei(cache_default_token_data, expected_result, value):
|
||||
assert from_wei(value) == expected_result
|
||||
assert from_wei(6, value) == expected_result
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value, expected_result", [
|
||||
@ -49,7 +48,7 @@ def test_from_wei(cache_default_token_data, expected_result, value):
|
||||
(0.10, 100000)
|
||||
])
|
||||
def test_to_wei(cache_default_token_data, expected_result, value):
|
||||
assert to_wei(value) == expected_result
|
||||
assert to_wei(6, value) == expected_result
|
||||
|
||||
|
||||
@pytest.mark.parametrize("decimals, value, expected_result", [
|
||||
@ -108,8 +107,8 @@ def test_outgoing_transaction_processor(activated_account,
|
||||
activated_account.blockchain_address,
|
||||
valid_recipient.blockchain_address)
|
||||
|
||||
outgoing_tx_processor.transfer(amount, token_symbol)
|
||||
outgoing_tx_processor.transfer(amount, 6, token_symbol)
|
||||
assert mock_transfer_api.get('from_address') == activated_account.blockchain_address
|
||||
assert mock_transfer_api.get('to_address') == valid_recipient.blockchain_address
|
||||
assert mock_transfer_api.get('value') == to_wei(amount)
|
||||
assert mock_transfer_api.get('value') == to_wei(6, amount)
|
||||
assert mock_transfer_api.get('token_symbol') == token_symbol
|
||||
|
@ -90,7 +90,7 @@ def test_standard_metadata_id(activated_account, cache_person_metadata, pending_
|
||||
|
||||
def test_account_create(init_cache, init_database, load_chain_spec, mock_account_creation_task_result, task_uuid):
|
||||
chain_str = Chain.spec.__str__()
|
||||
create(chain_str, phone_number(), init_database)
|
||||
create(chain_str, phone_number(), init_database, 'en')
|
||||
assert len(init_database.query(TaskTracker).all()) == 1
|
||||
account_creation_data = get_cached_data(task_uuid)
|
||||
assert json.loads(account_creation_data).get('status') == AccountStatus.PENDING.name
|
||||
|
@ -23,7 +23,7 @@ def test_ussd_metadata_handler(activated_account,
|
||||
setup_metadata_signer):
|
||||
identifier = bytes.fromhex(strip_0x(activated_account.blockchain_address))
|
||||
cic_type = MetadataPointer.PERSON
|
||||
metadata_client = UssdMetadataHandler(cic_type, identifier)
|
||||
metadata_client = UssdMetadataHandler(cic_type=cic_type, identifier=identifier)
|
||||
assert metadata_client.cic_type == cic_type
|
||||
assert metadata_client.engine == 'pgp'
|
||||
assert metadata_client.identifier == identifier
|
||||
|
72
apps/cic-ussd/tests/cic_ussd/metadata/test_tokens_meta.py
Normal file
72
apps/cic-ussd/tests/cic_ussd/metadata/test_tokens_meta.py
Normal file
@ -0,0 +1,72 @@
|
||||
# standard imports
|
||||
import json
|
||||
|
||||
# external imports
|
||||
import pytest
|
||||
import requests_mock
|
||||
from cic_types.condiments import MetadataPointer
|
||||
from requests.exceptions import HTTPError
|
||||
|
||||
# local imports
|
||||
from cic_ussd.cache import cache_data_key, get_cached_data
|
||||
from cic_ussd.metadata import TokenMetadata
|
||||
from cic_ussd.metadata.tokens import token_metadata_handler, query_token_metadata, query_token_info
|
||||
|
||||
|
||||
# test imports
|
||||
|
||||
|
||||
def test_token_metadata_handler(activated_account,
|
||||
init_cache,
|
||||
setup_metadata_request_handler,
|
||||
setup_metadata_signer,
|
||||
token_meta_symbol,
|
||||
token_symbol):
|
||||
with requests_mock.Mocker(real_http=False) as request_mocker:
|
||||
with pytest.raises(HTTPError) as error:
|
||||
metadata_client = TokenMetadata(identifier=b'foo', cic_type=MetadataPointer.TOKEN_META_SYMBOL)
|
||||
reason = 'Not Found'
|
||||
status_code = 401
|
||||
request_mocker.register_uri('GET', metadata_client.url, status_code=status_code, reason=reason)
|
||||
token_metadata_handler(metadata_client)
|
||||
assert str(error.value) == f'Client Error: {status_code}, reason: {reason}'
|
||||
|
||||
identifier = token_symbol.encode('utf-8')
|
||||
metadata_client = TokenMetadata(identifier, cic_type=MetadataPointer.TOKEN_META_SYMBOL)
|
||||
request_mocker.register_uri('GET', metadata_client.url, json=token_meta_symbol, status_code=200, reason='OK')
|
||||
token_metadata_handler(metadata_client)
|
||||
key = cache_data_key(identifier, MetadataPointer.TOKEN_META_SYMBOL)
|
||||
cached_token_meta_symbol = get_cached_data(key)
|
||||
assert json.loads(cached_token_meta_symbol) == token_meta_symbol
|
||||
|
||||
|
||||
def test_query_token_metadata(init_cache,
|
||||
setup_metadata_request_handler,
|
||||
setup_metadata_signer,
|
||||
token_meta_symbol,
|
||||
token_proof_symbol,
|
||||
token_symbol):
|
||||
with requests_mock.Mocker(real_http=False) as request_mocker:
|
||||
identifier = token_symbol.encode('utf-8')
|
||||
metadata_client = TokenMetadata(identifier, cic_type=MetadataPointer.TOKEN_META_SYMBOL)
|
||||
request_mocker.register_uri('GET', metadata_client.url, json=token_meta_symbol, status_code=200, reason='OK')
|
||||
query_token_metadata(identifier)
|
||||
key = cache_data_key(identifier, MetadataPointer.TOKEN_META_SYMBOL)
|
||||
cached_token_meta_symbol = get_cached_data(key)
|
||||
assert json.loads(cached_token_meta_symbol) == token_meta_symbol
|
||||
|
||||
|
||||
def test_query_token_info(init_cache,
|
||||
setup_metadata_request_handler,
|
||||
setup_metadata_signer,
|
||||
token_meta_symbol,
|
||||
token_proof_symbol,
|
||||
token_symbol):
|
||||
with requests_mock.Mocker(real_http=False) as request_mocker:
|
||||
identifier = token_symbol.encode('utf-8')
|
||||
metadata_client = TokenMetadata(identifier, cic_type=MetadataPointer.TOKEN_PROOF_SYMBOL)
|
||||
request_mocker.register_uri('GET', metadata_client.url, json=token_proof_symbol, status_code=200, reason='OK')
|
||||
query_token_info(identifier)
|
||||
key = cache_data_key(identifier, MetadataPointer.TOKEN_PROOF_SYMBOL)
|
||||
cached_token_proof_symbol = get_cached_data(key)
|
||||
assert json.loads(cached_token_proof_symbol) == token_proof_symbol
|
@ -1,6 +1,6 @@
|
||||
# standard imports
|
||||
import json
|
||||
import datetime
|
||||
import os
|
||||
|
||||
# external imports
|
||||
from cic_types.condiments import MetadataPointer
|
||||
@ -10,195 +10,464 @@ from cic_ussd.account.balance import get_cached_available_balance
|
||||
from cic_ussd.account.metadata import get_cached_preferred_language
|
||||
from cic_ussd.account.statement import (
|
||||
get_cached_statement,
|
||||
parse_statement_transactions,
|
||||
statement_transaction_set
|
||||
parse_statement_transactions
|
||||
)
|
||||
from cic_ussd.account.tokens import get_default_token_symbol
|
||||
from cic_ussd.account.tokens import (get_active_token_symbol,
|
||||
get_cached_token_data)
|
||||
from cic_ussd.account.transaction import from_wei, to_wei
|
||||
from cic_ussd.cache import cache_data, cache_data_key
|
||||
from cic_ussd.menu.ussd_menu import UssdMenu
|
||||
from cic_ussd.cache import cache_data, cache_data_key, get_cached_data
|
||||
from cic_ussd.metadata import PersonMetadata
|
||||
from cic_ussd.phone_number import Support
|
||||
from cic_ussd.processor.menu import response
|
||||
from cic_ussd.processor.util import parse_person_metadata
|
||||
from cic_ussd.processor.menu import response, MenuProcessor
|
||||
from cic_ussd.processor.util import parse_person_metadata, ussd_menu_list
|
||||
from cic_ussd.translation import translation_for
|
||||
|
||||
|
||||
# test imports
|
||||
|
||||
|
||||
def test_menu_processor(activated_account,
|
||||
balances,
|
||||
cache_balances,
|
||||
cache_default_token_data,
|
||||
cache_preferences,
|
||||
cache_person_metadata,
|
||||
cache_statement,
|
||||
celery_session_worker,
|
||||
generic_ussd_session,
|
||||
init_database,
|
||||
load_chain_spec,
|
||||
load_support_phone,
|
||||
load_ussd_menu,
|
||||
mock_get_adjusted_balance,
|
||||
mock_sync_balance_api_query,
|
||||
mock_transaction_list_query,
|
||||
valid_recipient):
|
||||
preferred_language = get_cached_preferred_language(activated_account.blockchain_address)
|
||||
available_balance = get_cached_available_balance(activated_account.blockchain_address)
|
||||
token_symbol = get_default_token_symbol()
|
||||
def test_account_balance(activated_account, cache_balances, cache_preferences, cache_token_data,
|
||||
generic_ussd_session, init_database, set_active_token):
|
||||
"""blockchain_address = activated_account.blockchain_address
|
||||
token_symbol = get_active_token_symbol(blockchain_address)
|
||||
token_data = get_cached_token_data(blockchain_address, token_symbol)
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
decimals = token_data.get("decimals")
|
||||
identifier = bytes.fromhex(blockchain_address)
|
||||
balances_identifier = [identifier, token_symbol.encode('utf-8')]
|
||||
available_balance = get_cached_available_balance(decimals, balances_identifier)
|
||||
with_available_balance = 'ussd.account_balances.available_balance'
|
||||
with_fees = 'ussd.account_balances.with_fees'
|
||||
ussd_menu = UssdMenu.find_by_name('account_balances')
|
||||
name = ussd_menu.get('name')
|
||||
resp = response(activated_account, 'ussd.account_balances', name, init_database, generic_ussd_session)
|
||||
resp = response(activated_account, with_available_balance, with_available_balance[5:], init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == translation_for(with_available_balance,
|
||||
preferred_language,
|
||||
available_balance=available_balance,
|
||||
token_symbol=token_symbol)
|
||||
|
||||
identifier = bytes.fromhex(activated_account.blockchain_address)
|
||||
key = cache_data_key(identifier, MetadataPointer.BALANCES_ADJUSTED)
|
||||
with_fees = 'ussd.account_balances.with_fees'
|
||||
key = cache_data_key(balances_identifier, MetadataPointer.BALANCES_ADJUSTED)
|
||||
adjusted_balance = 45931650.64654012
|
||||
cache_data(key, json.dumps(adjusted_balance))
|
||||
resp = response(activated_account, 'ussd.account_balances', name, init_database, generic_ussd_session)
|
||||
tax_wei = to_wei(int(available_balance)) - int(adjusted_balance)
|
||||
tax = from_wei(int(tax_wei))
|
||||
resp = response(activated_account, with_fees, with_fees[5:], init_database, generic_ussd_session)
|
||||
tax_wei = to_wei(decimals, int(available_balance)) - int(adjusted_balance)
|
||||
tax = from_wei(decimals, int(tax_wei))
|
||||
assert resp == translation_for(key=with_fees,
|
||||
preferred_language=preferred_language,
|
||||
available_balance=available_balance,
|
||||
tax=tax,
|
||||
token_symbol=token_symbol)
|
||||
token_symbol=token_symbol)"""
|
||||
pass
|
||||
|
||||
cached_statement = get_cached_statement(activated_account.blockchain_address)
|
||||
statement = json.loads(cached_statement)
|
||||
statement_transactions = parse_statement_transactions(statement)
|
||||
transaction_sets = [statement_transactions[tx:tx + 3] for tx in range(0, len(statement_transactions), 3)]
|
||||
first_transaction_set = []
|
||||
middle_transaction_set = []
|
||||
last_transaction_set = []
|
||||
if transaction_sets:
|
||||
first_transaction_set = statement_transaction_set(preferred_language, transaction_sets[0])
|
||||
if len(transaction_sets) >= 2:
|
||||
middle_transaction_set = statement_transaction_set(preferred_language, transaction_sets[1])
|
||||
if len(transaction_sets) >= 3:
|
||||
last_transaction_set = statement_transaction_set(preferred_language, transaction_sets[2])
|
||||
|
||||
display_key = 'ussd.first_transaction_set'
|
||||
ussd_menu = UssdMenu.find_by_name('first_transaction_set')
|
||||
name = ussd_menu.get('name')
|
||||
resp = response(activated_account, display_key, name, init_database, generic_ussd_session)
|
||||
def test_account_statement(activated_account,
|
||||
cache_preferences,
|
||||
cache_statement,
|
||||
generic_ussd_session,
|
||||
init_database,
|
||||
set_active_token,
|
||||
set_locale_files):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
cached_statement = get_cached_statement(blockchain_address)
|
||||
statement_list = parse_statement_transactions(statement=json.loads(cached_statement))
|
||||
first_transaction_set = 'ussd.first_transaction_set'
|
||||
middle_transaction_set = 'ussd.middle_transaction_set'
|
||||
last_transaction_set = 'ussd.last_transaction_set'
|
||||
fallback = translation_for('helpers.no_transaction_history', preferred_language)
|
||||
transaction_sets = ussd_menu_list(fallback=fallback, menu_list=statement_list, split=3)
|
||||
resp = response(activated_account, first_transaction_set, first_transaction_set[5:], init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == translation_for(first_transaction_set, preferred_language, first_transaction_set=transaction_sets[0])
|
||||
resp = response(activated_account, middle_transaction_set, middle_transaction_set[5:], init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == translation_for(middle_transaction_set, preferred_language,
|
||||
middle_transaction_set=transaction_sets[1])
|
||||
resp = response(activated_account, last_transaction_set, last_transaction_set[5:], init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == translation_for(last_transaction_set, preferred_language, last_transaction_set=transaction_sets[2])
|
||||
|
||||
assert resp == translation_for(display_key, preferred_language, first_transaction_set=first_transaction_set)
|
||||
|
||||
display_key = 'ussd.middle_transaction_set'
|
||||
ussd_menu = UssdMenu.find_by_name('middle_transaction_set')
|
||||
name = ussd_menu.get('name')
|
||||
resp = response(activated_account, display_key, name, init_database, generic_ussd_session)
|
||||
def test_add_guardian_pin_authorization(activated_account,
|
||||
cache_preferences,
|
||||
guardian_account,
|
||||
generic_ussd_session,
|
||||
init_database):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
add_guardian_pin_authorization = 'ussd.add_guardian_pin_authorization'
|
||||
activated_account.add_guardian(guardian_account.phone_number)
|
||||
init_database.flush()
|
||||
generic_ussd_session['external_session_id'] = os.urandom(20).hex()
|
||||
generic_ussd_session['msisdn'] = guardian_account.phone_number
|
||||
generic_ussd_session['data'] = {'guardian_phone_number': guardian_account.phone_number}
|
||||
generic_ussd_session['state'] = 'add_guardian_pin_authorization'
|
||||
resp = response(activated_account,
|
||||
add_guardian_pin_authorization,
|
||||
add_guardian_pin_authorization[5:],
|
||||
init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == translation_for(f'{add_guardian_pin_authorization}.first', preferred_language,
|
||||
guardian_information=guardian_account.standard_metadata_id())
|
||||
|
||||
assert resp == translation_for(display_key, preferred_language, middle_transaction_set=middle_transaction_set)
|
||||
|
||||
display_key = 'ussd.last_transaction_set'
|
||||
ussd_menu = UssdMenu.find_by_name('last_transaction_set')
|
||||
name = ussd_menu.get('name')
|
||||
resp = response(activated_account, display_key, name, init_database, generic_ussd_session)
|
||||
def test_guardian_list(activated_account,
|
||||
cache_preferences,
|
||||
generic_ussd_session,
|
||||
guardian_account,
|
||||
init_database):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
guardians_list = 'ussd.guardian_list'
|
||||
guardians_list_header = translation_for('helpers.guardians_list_header', preferred_language)
|
||||
guardian_information = guardian_account.standard_metadata_id()
|
||||
guardians = guardians_list_header + '\n' + f'{guardian_information}\n'
|
||||
activated_account.add_guardian(guardian_account.phone_number)
|
||||
init_database.flush()
|
||||
resp = response(activated_account, guardians_list, guardians_list[5:], init_database, generic_ussd_session)
|
||||
assert resp == translation_for(guardians_list, preferred_language, guardians_list=guardians)
|
||||
guardians = translation_for('helpers.no_guardians_list', preferred_language)
|
||||
identifier = bytes.fromhex(guardian_account.blockchain_address)
|
||||
key = cache_data_key(identifier, MetadataPointer.PREFERENCES)
|
||||
cache_data(key, json.dumps({'preferred_language': preferred_language}))
|
||||
resp = response(guardian_account, guardians_list, guardians_list[5:], init_database, generic_ussd_session)
|
||||
assert resp == translation_for(guardians_list, preferred_language, guardians_list=guardians)
|
||||
|
||||
assert resp == translation_for(display_key, preferred_language, last_transaction_set=last_transaction_set)
|
||||
|
||||
display_key = 'ussd.display_user_metadata'
|
||||
ussd_menu = UssdMenu.find_by_name('display_user_metadata')
|
||||
name = ussd_menu.get('name')
|
||||
identifier = bytes.fromhex(activated_account.blockchain_address)
|
||||
def test_account_tokens(activated_account, cache_token_data_list, celery_session_worker, generic_ussd_session,
|
||||
init_cache, init_database):
|
||||
"""blockchain_address = activated_account.blockchain_address
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
cached_token_data_list = get_cached_token_data_list(blockchain_address)
|
||||
token_data_list = ['1. GFT 50.0']
|
||||
fallback = translation_for('helpers.no_tokens_list', preferred_language)
|
||||
token_list_sets = ussd_menu_list(fallback=fallback, menu_list=token_data_list, split=3)
|
||||
first_account_tokens_set = 'ussd.first_account_tokens_set'
|
||||
middle_account_tokens_set = 'ussd.middle_account_tokens_set'
|
||||
last_account_tokens_set = 'ussd.last_account_tokens_set'
|
||||
resp = response(activated_account, first_account_tokens_set, first_account_tokens_set[5:], init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == translation_for(first_account_tokens_set, preferred_language,
|
||||
first_account_tokens_set=token_list_sets[0])
|
||||
assert generic_ussd_session.get('data').get('account_tokens_list') == cached_token_data_list
|
||||
resp = response(activated_account, middle_account_tokens_set, middle_account_tokens_set[5:], init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == translation_for(middle_account_tokens_set, preferred_language,
|
||||
middle_account_tokens_set=token_list_sets[1])
|
||||
resp = response(activated_account, last_account_tokens_set, last_account_tokens_set[5:], init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == translation_for(last_account_tokens_set, preferred_language,
|
||||
last_account_tokens_set=token_list_sets[2])"""
|
||||
pass
|
||||
|
||||
|
||||
def test_help(activated_account, cache_preferences, generic_ussd_session, init_database):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
help = 'ussd.help'
|
||||
resp = response(activated_account, help, help[5:], init_database, generic_ussd_session)
|
||||
assert resp == translation_for(help, preferred_language, support_phone=Support.phone_number)
|
||||
|
||||
|
||||
def test_person_data(activated_account, cache_person_metadata, cache_preferences, cached_ussd_session,
|
||||
generic_ussd_session, init_database):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
identifier = bytes.fromhex(blockchain_address)
|
||||
display_user_metadata = 'ussd.display_user_metadata'
|
||||
person_metadata = PersonMetadata(identifier)
|
||||
cached_person_metadata = person_metadata.get_cached_metadata()
|
||||
resp = response(activated_account, display_key, name, init_database, generic_ussd_session)
|
||||
assert resp == parse_person_metadata(cached_person_metadata, display_key, preferred_language)
|
||||
resp = response(activated_account, display_user_metadata, display_user_metadata[5:], init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == parse_person_metadata(cached_person_metadata, display_user_metadata, preferred_language)
|
||||
|
||||
display_key = 'ussd.account_balances_pin_authorization'
|
||||
ussd_menu = UssdMenu.find_by_name('account_balances_pin_authorization')
|
||||
name = ussd_menu.get('name')
|
||||
resp = response(activated_account, display_key, name, init_database, generic_ussd_session)
|
||||
assert resp == translation_for(f'{display_key}.first', preferred_language)
|
||||
|
||||
activated_account.failed_pin_attempts = 1
|
||||
resp = response(activated_account, display_key, name, init_database, generic_ussd_session)
|
||||
retry_pin_entry = translation_for('ussd.retry_pin_entry', preferred_language, remaining_attempts=2)
|
||||
assert resp == translation_for(f'{display_key}.retry', preferred_language, retry_pin_entry=retry_pin_entry)
|
||||
activated_account.failed_pin_attempts = 0
|
||||
def test_guarded_account_metadata(activated_account, generic_ussd_session, init_database):
|
||||
reset_guarded_pin_authorization = 'ussd.reset_guarded_pin_authorization'
|
||||
generic_ussd_session['data'] = {'guarded_account_phone_number': activated_account.phone_number}
|
||||
menu_processor = MenuProcessor(activated_account, reset_guarded_pin_authorization,
|
||||
reset_guarded_pin_authorization[5:], init_database, generic_ussd_session)
|
||||
assert menu_processor.guarded_account_metadata() == activated_account.standard_metadata_id()
|
||||
|
||||
display_key = 'ussd.start'
|
||||
ussd_menu = UssdMenu.find_by_name('start')
|
||||
name = ussd_menu.get('name')
|
||||
resp = response(activated_account, display_key, name, init_database, generic_ussd_session)
|
||||
assert resp == translation_for(display_key,
|
||||
|
||||
def test_guardian_metadata(activated_account, generic_ussd_session, guardian_account, init_database):
|
||||
add_guardian_pin_authorization = 'ussd.add_guardian_pin_authorization'
|
||||
generic_ussd_session['data'] = {'guardian_phone_number': guardian_account.phone_number}
|
||||
menu_processor = MenuProcessor(activated_account, add_guardian_pin_authorization,
|
||||
add_guardian_pin_authorization[5:], init_database, generic_ussd_session)
|
||||
assert menu_processor.guardian_metadata() == guardian_account.standard_metadata_id()
|
||||
|
||||
|
||||
def test_language(activated_account, cache_preferences, generic_ussd_session, init_database, load_languages):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
initial_language_selection = 'ussd.initial_language_selection'
|
||||
select_preferred_language = 'ussd.select_preferred_language'
|
||||
initial_middle_language_set = 'ussd.initial_middle_language_set'
|
||||
middle_language_set = 'ussd.middle_language_set'
|
||||
initial_last_language_set = 'ussd.initial_last_language_set'
|
||||
last_language_set = 'ussd.last_language_set'
|
||||
|
||||
key = cache_data_key('system:languages'.encode('utf-8'), MetadataPointer.NONE)
|
||||
cached_system_languages = get_cached_data(key)
|
||||
language_list: list = json.loads(cached_system_languages)
|
||||
|
||||
fallback = translation_for('helpers.no_language_list', preferred_language)
|
||||
language_list_sets = ussd_menu_list(fallback=fallback, menu_list=language_list, split=3)
|
||||
|
||||
resp = response(activated_account, initial_language_selection, initial_language_selection[5:], init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == translation_for(initial_language_selection, preferred_language,
|
||||
first_language_set=language_list_sets[0])
|
||||
|
||||
resp = response(activated_account, select_preferred_language, select_preferred_language[5:], init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == translation_for(select_preferred_language, preferred_language,
|
||||
first_language_set=language_list_sets[0])
|
||||
|
||||
resp = response(activated_account, initial_middle_language_set, initial_middle_language_set[5:], init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == translation_for(initial_middle_language_set, preferred_language,
|
||||
middle_language_set=language_list_sets[1])
|
||||
|
||||
resp = response(activated_account, initial_last_language_set, initial_last_language_set[5:], init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == translation_for(initial_last_language_set, preferred_language,
|
||||
last_language_set=language_list_sets[2])
|
||||
|
||||
resp = response(activated_account, middle_language_set, middle_language_set[5:], init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == translation_for(middle_language_set, preferred_language, middle_language_set=language_list_sets[1])
|
||||
|
||||
resp = response(activated_account, last_language_set, last_language_set[5:], init_database, generic_ussd_session)
|
||||
assert resp == translation_for(last_language_set, preferred_language, last_language_set=language_list_sets[2])
|
||||
|
||||
|
||||
def test_account_creation_prompt(activated_account, cache_preferences, generic_ussd_session, init_database,
|
||||
load_languages):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
user_input = ''
|
||||
if preferred_language == 'en':
|
||||
user_input = '1'
|
||||
elif preferred_language == 'sw':
|
||||
user_input = '2'
|
||||
account_creation_prompt = 'ussd.account_creation_prompt'
|
||||
generic_ussd_session['user_input'] = user_input
|
||||
resp = response(activated_account, account_creation_prompt, account_creation_prompt[5:], init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == translation_for(account_creation_prompt, preferred_language)
|
||||
|
||||
|
||||
def test_reset_guarded_pin_authorization(activated_account, cache_preferences, generic_ussd_session, guardian_account,
|
||||
init_database):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
reset_guarded_pin_authorization = 'ussd.reset_guarded_pin_authorization'
|
||||
generic_ussd_session['external_session_id'] = os.urandom(20).hex()
|
||||
generic_ussd_session['msisdn'] = guardian_account.phone_number
|
||||
generic_ussd_session['data'] = {'guarded_account_phone_number': activated_account.phone_number}
|
||||
resp = response(activated_account,
|
||||
reset_guarded_pin_authorization,
|
||||
reset_guarded_pin_authorization[5:],
|
||||
init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == translation_for(f'{reset_guarded_pin_authorization}.first', preferred_language,
|
||||
guarded_account_information=activated_account.phone_number)
|
||||
|
||||
|
||||
def test_start(activated_account, cache_balances, cache_preferences, cache_token_data, cache_token_data_list,
|
||||
cache_token_symbol_list, celery_session_worker, generic_ussd_session, init_database, load_chain_spec,
|
||||
mock_sync_balance_api_query, set_active_token):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
token_symbol = get_active_token_symbol(blockchain_address)
|
||||
token_data = get_cached_token_data(blockchain_address, token_symbol)
|
||||
decimals = token_data.get("decimals")
|
||||
identifier = bytes.fromhex(blockchain_address)
|
||||
balances_identifier = [identifier, token_symbol.encode('utf-8')]
|
||||
available_balance = get_cached_available_balance(decimals, balances_identifier)
|
||||
start = 'ussd.start'
|
||||
resp = response(activated_account, start, start[5:], init_database, generic_ussd_session)
|
||||
assert resp == translation_for(start,
|
||||
preferred_language,
|
||||
account_balance=available_balance,
|
||||
account_token_name=token_symbol)
|
||||
|
||||
display_key = 'ussd.start'
|
||||
ussd_menu = UssdMenu.find_by_name('start')
|
||||
name = ussd_menu.get('name')
|
||||
older_timestamp = (activated_account.created - datetime.timedelta(days=35))
|
||||
activated_account.created = older_timestamp
|
||||
init_database.flush()
|
||||
response(activated_account, display_key, name, init_database, generic_ussd_session)
|
||||
assert mock_get_adjusted_balance['timestamp'] == int((datetime.datetime.now() - datetime.timedelta(days=30)).timestamp())
|
||||
|
||||
display_key = 'ussd.transaction_pin_authorization'
|
||||
ussd_menu = UssdMenu.find_by_name('transaction_pin_authorization')
|
||||
name = ussd_menu.get('name')
|
||||
def test_token_selection_pin_authorization(activated_account, cache_preferences, cache_token_data, generic_ussd_session,
|
||||
init_database, set_active_token):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
token_symbol = get_active_token_symbol(blockchain_address)
|
||||
token_data = get_cached_token_data(blockchain_address, token_symbol)
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
token_selection_pin_authorization = 'ussd.token_selection_pin_authorization'
|
||||
generic_ussd_session['data'] = {'selected_token': token_data}
|
||||
resp = response(activated_account,
|
||||
token_selection_pin_authorization,
|
||||
token_selection_pin_authorization[5:],
|
||||
init_database,
|
||||
generic_ussd_session)
|
||||
token_name = token_data.get('name')
|
||||
token_symbol = token_data.get('symbol')
|
||||
token_issuer = token_data.get('issuer')
|
||||
token_contact = token_data.get('contact')
|
||||
token_location = token_data.get('location')
|
||||
data = f'{token_name} ({token_symbol})\n{token_issuer}\n{token_contact}\n{token_location}\n'
|
||||
assert resp == translation_for(f'{token_selection_pin_authorization}.first', preferred_language,
|
||||
token_data=data)
|
||||
|
||||
|
||||
def test_transaction_pin_authorization(activated_account, cache_preferences, cache_token_data, generic_ussd_session,
|
||||
init_database, set_active_token, valid_recipient):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
token_symbol = get_active_token_symbol(blockchain_address)
|
||||
token_data = get_cached_token_data(blockchain_address, token_symbol)
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
decimals = token_data.get("decimals")
|
||||
transaction_pin_authorization = 'ussd.transaction_pin_authorization'
|
||||
generic_ussd_session['data'] = {
|
||||
'recipient_phone_number': valid_recipient.phone_number,
|
||||
'transaction_amount': '15'
|
||||
}
|
||||
resp = response(activated_account, display_key, name, init_database, generic_ussd_session)
|
||||
resp = response(activated_account, transaction_pin_authorization, transaction_pin_authorization[5:], init_database,
|
||||
generic_ussd_session)
|
||||
user_input = generic_ussd_session.get('data').get('transaction_amount')
|
||||
transaction_amount = to_wei(value=int(user_input))
|
||||
transaction_amount = to_wei(decimals, int(user_input))
|
||||
tx_recipient_information = valid_recipient.standard_metadata_id()
|
||||
tx_sender_information = activated_account.standard_metadata_id()
|
||||
assert resp == translation_for(f'{display_key}.first',
|
||||
assert resp == translation_for(f'{transaction_pin_authorization}.first',
|
||||
preferred_language,
|
||||
recipient_information=tx_recipient_information,
|
||||
transaction_amount=from_wei(transaction_amount),
|
||||
transaction_amount=from_wei(decimals, transaction_amount),
|
||||
token_symbol=token_symbol,
|
||||
sender_information=tx_sender_information)
|
||||
|
||||
display_key = 'ussd.exit_insufficient_balance'
|
||||
ussd_menu = UssdMenu.find_by_name('exit_insufficient_balance')
|
||||
name = ussd_menu.get('name')
|
||||
|
||||
def test_guardian_exits(activated_account, cache_preferences, cache_token_data, generic_ussd_session, guardian_account,
|
||||
init_database, set_active_token):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
generic_ussd_session['data'] = {'guardian_phone_number': guardian_account.phone_number}
|
||||
# testing exit guardian addition success
|
||||
exit_guardian_addition_success = 'ussd.exit_guardian_addition_success'
|
||||
resp = response(activated_account, exit_guardian_addition_success, exit_guardian_addition_success[5:],
|
||||
init_database, generic_ussd_session)
|
||||
assert resp == translation_for(exit_guardian_addition_success, preferred_language,
|
||||
guardian_information=guardian_account.standard_metadata_id())
|
||||
|
||||
# testing exit guardian removal success
|
||||
exit_guardian_removal_success = 'ussd.exit_guardian_removal_success'
|
||||
resp = response(activated_account, exit_guardian_removal_success, exit_guardian_removal_success[5:],
|
||||
init_database, generic_ussd_session)
|
||||
assert resp == translation_for(exit_guardian_removal_success, preferred_language,
|
||||
guardian_information=guardian_account.standard_metadata_id())
|
||||
|
||||
generic_ussd_session['data'] = {'failure_reason': 'foo'}
|
||||
# testing exit invalid guardian addition
|
||||
exit_invalid_guardian_addition = 'ussd.exit_invalid_guardian_addition'
|
||||
resp = response(activated_account, exit_invalid_guardian_addition, exit_invalid_guardian_addition[5:],
|
||||
init_database, generic_ussd_session)
|
||||
assert resp == translation_for(exit_invalid_guardian_addition, preferred_language, error_exit='foo')
|
||||
|
||||
# testing exit invalid guardian removal
|
||||
exit_invalid_guardian_removal = 'ussd.exit_invalid_guardian_removal'
|
||||
resp = response(activated_account, exit_invalid_guardian_removal, exit_invalid_guardian_removal[5:],
|
||||
init_database, generic_ussd_session)
|
||||
assert resp == translation_for(exit_invalid_guardian_removal, preferred_language, error_exit='foo')
|
||||
|
||||
|
||||
def test_exit_pin_reset_initiated_success(activated_account, cache_preferences, generic_ussd_session, init_database):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
exit_pin_reset_initiated_success = 'ussd.exit_pin_reset_initiated_success'
|
||||
generic_ussd_session['data'] = {'guarded_account_phone_number': activated_account.phone_number}
|
||||
resp = response(activated_account, exit_pin_reset_initiated_success, exit_pin_reset_initiated_success[5:],
|
||||
init_database, generic_ussd_session)
|
||||
assert resp == translation_for(exit_pin_reset_initiated_success,
|
||||
preferred_language,
|
||||
guarded_account_information=activated_account.standard_metadata_id())
|
||||
|
||||
|
||||
def test_exit_insufficient_balance(activated_account, cache_balances, cache_preferences, cache_token_data,
|
||||
generic_ussd_session, init_database, set_active_token, valid_recipient):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
token_symbol = get_active_token_symbol(blockchain_address)
|
||||
token_data = get_cached_token_data(blockchain_address, token_symbol)
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
decimals = token_data.get("decimals")
|
||||
identifier = bytes.fromhex(blockchain_address)
|
||||
balances_identifier = [identifier, token_symbol.encode('utf-8')]
|
||||
available_balance = get_cached_available_balance(decimals, balances_identifier)
|
||||
tx_recipient_information = valid_recipient.standard_metadata_id()
|
||||
exit_insufficient_balance = 'ussd.exit_insufficient_balance'
|
||||
generic_ussd_session['data'] = {
|
||||
'recipient_phone_number': valid_recipient.phone_number,
|
||||
'transaction_amount': '85'
|
||||
}
|
||||
transaction_amount = generic_ussd_session.get('data').get('transaction_amount')
|
||||
transaction_amount = to_wei(value=int(transaction_amount))
|
||||
resp = response(activated_account, display_key, name, init_database, generic_ussd_session)
|
||||
assert resp == translation_for(display_key,
|
||||
transaction_amount = to_wei(decimals, int(transaction_amount))
|
||||
resp = response(activated_account, exit_insufficient_balance, exit_insufficient_balance[5:], init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == translation_for(exit_insufficient_balance,
|
||||
preferred_language,
|
||||
amount=from_wei(transaction_amount),
|
||||
amount=from_wei(decimals, transaction_amount),
|
||||
token_symbol=token_symbol,
|
||||
recipient_information=tx_recipient_information,
|
||||
token_balance=available_balance)
|
||||
|
||||
display_key = 'ussd.exit_invalid_menu_option'
|
||||
ussd_menu = UssdMenu.find_by_name('exit_invalid_menu_option')
|
||||
name = ussd_menu.get('name')
|
||||
resp = response(activated_account, display_key, name, init_database, generic_ussd_session)
|
||||
assert resp == translation_for(display_key, preferred_language, support_phone=Support.phone_number)
|
||||
|
||||
display_key = 'ussd.exit_successful_transaction'
|
||||
ussd_menu = UssdMenu.find_by_name('exit_successful_transaction')
|
||||
name = ussd_menu.get('name')
|
||||
def test_exit_invalid_menu_option(activated_account, cache_preferences, generic_ussd_session, init_database,
|
||||
load_support_phone):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
exit_invalid_menu_option = 'ussd.exit_invalid_menu_option'
|
||||
resp = response(activated_account, exit_invalid_menu_option, exit_invalid_menu_option[5:], init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == translation_for(exit_invalid_menu_option, preferred_language, support_phone=Support.phone_number)
|
||||
|
||||
|
||||
def test_exit_pin_blocked(activated_account, cache_preferences, generic_ussd_session, init_database,
|
||||
load_support_phone):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
exit_pin_blocked = 'ussd.exit_pin_blocked'
|
||||
resp = response(activated_account, exit_pin_blocked, exit_pin_blocked[5:], init_database, generic_ussd_session)
|
||||
assert resp == translation_for(exit_pin_blocked, preferred_language, support_phone=Support.phone_number)
|
||||
|
||||
|
||||
def test_exit_successful_token_selection(activated_account, cache_preferences, cache_token_data, generic_ussd_session,
|
||||
init_database, set_active_token):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
token_symbol = get_active_token_symbol(blockchain_address)
|
||||
token_data = get_cached_token_data(blockchain_address, token_symbol)
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
exit_successful_token_selection = 'ussd.exit_successful_token_selection'
|
||||
generic_ussd_session['data'] = {'selected_token': token_data}
|
||||
resp = response(activated_account, exit_successful_token_selection, exit_successful_token_selection[5:],
|
||||
init_database, generic_ussd_session)
|
||||
assert resp == translation_for(exit_successful_token_selection, preferred_language, token_symbol=token_symbol)
|
||||
|
||||
|
||||
def test_exit_successful_transaction(activated_account, cache_preferences, cache_token_data, generic_ussd_session,
|
||||
init_database, set_active_token, valid_recipient):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
token_symbol = get_active_token_symbol(blockchain_address)
|
||||
token_data = get_cached_token_data(blockchain_address, token_symbol)
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
decimals = token_data.get("decimals")
|
||||
tx_recipient_information = valid_recipient.standard_metadata_id()
|
||||
tx_sender_information = activated_account.standard_metadata_id()
|
||||
exit_successful_transaction = 'ussd.exit_successful_transaction'
|
||||
generic_ussd_session['data'] = {
|
||||
'recipient_phone_number': valid_recipient.phone_number,
|
||||
'transaction_amount': '15'
|
||||
}
|
||||
transaction_amount = generic_ussd_session.get('data').get('transaction_amount')
|
||||
transaction_amount = to_wei(value=int(transaction_amount))
|
||||
resp = response(activated_account, display_key, name, init_database, generic_ussd_session)
|
||||
assert resp == translation_for(display_key,
|
||||
transaction_amount = to_wei(decimals, int(transaction_amount))
|
||||
resp = response(activated_account, exit_successful_transaction, exit_successful_transaction[5:], init_database,
|
||||
generic_ussd_session)
|
||||
assert resp == translation_for(exit_successful_transaction,
|
||||
preferred_language,
|
||||
transaction_amount=from_wei(transaction_amount),
|
||||
transaction_amount=from_wei(decimals, transaction_amount),
|
||||
token_symbol=token_symbol,
|
||||
recipient_information=tx_recipient_information,
|
||||
sender_information=tx_sender_information)
|
||||
|
69
apps/cic-ussd/tests/cic_ussd/processor/test_poller.py
Normal file
69
apps/cic-ussd/tests/cic_ussd/processor/test_poller.py
Normal file
@ -0,0 +1,69 @@
|
||||
# standard imports
|
||||
import logging
|
||||
import time
|
||||
from queue import Queue
|
||||
|
||||
# external imports
|
||||
import pytest
|
||||
from cic_types.condiments import MetadataPointer
|
||||
|
||||
# local imports
|
||||
from cic_ussd.cache import cache_data, cache_data_key, get_cached_data
|
||||
from cic_ussd.error import MaxRetryReached
|
||||
from cic_ussd.processor.poller import poller, wait_for_cache, wait_for_session_data
|
||||
|
||||
# test imports
|
||||
|
||||
|
||||
def test_poller(activated_account, caplog, init_cache, token_symbol):
|
||||
caplog.set_level(logging.DEBUG)
|
||||
identifier = bytes.fromhex(activated_account.blockchain_address)
|
||||
key = cache_data_key(identifier, MetadataPointer.TOKEN_ACTIVE)
|
||||
with pytest.raises(MaxRetryReached) as error:
|
||||
interval = 1
|
||||
max_retry = 3
|
||||
collected_values = [None, None, None]
|
||||
poller(args=(key,), interval=interval, kwargs=None, max_retry=max_retry, target=get_cached_data)
|
||||
assert str(error.value) == str(MaxRetryReached(collected_values, None))
|
||||
cache_data(key, token_symbol)
|
||||
poller(args=(key,), interval=interval, kwargs=None, max_retry=max_retry, target=get_cached_data)
|
||||
assert f'Resource: {token_symbol} now available.' in caplog.text
|
||||
|
||||
|
||||
def test_wait_for_cache(activated_account, caplog, init_cache, token_symbol):
|
||||
caplog.set_level(logging.DEBUG)
|
||||
identifier = bytes.fromhex(activated_account.blockchain_address)
|
||||
key = cache_data_key(identifier, MetadataPointer.TOKEN_ACTIVE)
|
||||
cache_data(key, token_symbol)
|
||||
interval = 1
|
||||
max_retry = 3
|
||||
resource_name = 'Active Token'
|
||||
wait_for_cache(identifier, resource_name, MetadataPointer.TOKEN_ACTIVE, interval, max_retry)
|
||||
assert f'Polling for resource: {resource_name} at: {key} every: {interval} second(s) for {max_retry} seconds.' in caplog.text
|
||||
|
||||
|
||||
def test_wait_for_session_data(activated_account, caplog, generic_ussd_session):
|
||||
caplog.set_level(logging.DEBUG)
|
||||
generic_ussd_session.__delitem__('data')
|
||||
interval = 1
|
||||
max_retry = 3
|
||||
collected_values = [None, None, None]
|
||||
resource_name = 'Foo Data'
|
||||
session_data_key = 'foo'
|
||||
with pytest.raises(MaxRetryReached) as error:
|
||||
wait_for_session_data(resource_name, session_data_key, generic_ussd_session, interval, max_retry)
|
||||
assert str(error.value) == str(MaxRetryReached(collected_values, None))
|
||||
assert f'Data poller with max retry at: {max_retry}. Checking for every: {interval} seconds.' in caplog.text
|
||||
generic_ussd_session['data'] = {}
|
||||
with pytest.raises(MaxRetryReached) as error:
|
||||
collected_values = [None, None, None]
|
||||
wait_for_session_data(resource_name, session_data_key, generic_ussd_session, interval, max_retry)
|
||||
assert f'Data poller with max retry at: {max_retry}. Checking for every: {interval} seconds.' in caplog.text
|
||||
assert f'Session data poller for: {resource_name} with max retry at: {max_retry}. Checking for every: {interval} seconds.' in caplog.text
|
||||
assert str(error.value) == str(MaxRetryReached(collected_values, None))
|
||||
expected_value = 'bar'
|
||||
generic_ussd_session['data'] = {'foo': expected_value}
|
||||
wait_for_session_data(resource_name, session_data_key, generic_ussd_session, interval, max_retry)
|
||||
assert f'Data poller with max retry at: {max_retry}. Checking for every: {interval} seconds.' in caplog.text
|
||||
assert f'Session data poller for: {resource_name} with max retry at: {max_retry}. Checking for every: {interval} seconds.' in caplog.text
|
||||
assert f'Resource: {expected_value} now available.' in caplog.text
|
@ -10,13 +10,16 @@ from chainlib.hash import strip_0x
|
||||
from cic_types.condiments import MetadataPointer
|
||||
|
||||
# local imports
|
||||
from cic_ussd.account.chain import Chain
|
||||
from cic_ussd.account.metadata import get_cached_preferred_language
|
||||
from cic_ussd.cache import cache_data, cache_data_key, get_cached_data
|
||||
from cic_ussd.db.models.task_tracker import TaskTracker
|
||||
from cic_ussd.menu.ussd_menu import UssdMenu
|
||||
from cic_ussd.metadata import PersonMetadata
|
||||
from cic_ussd.processor.ussd import get_menu, handle_menu, handle_menu_operations
|
||||
from cic_ussd.processor.ussd import (get_menu,
|
||||
handle_menu,
|
||||
handle_menu_operations)
|
||||
from cic_ussd.processor.util import ussd_menu_list
|
||||
from cic_ussd.state_machine.logic.language import preferred_langauge_from_selection
|
||||
from cic_ussd.translation import translation_for
|
||||
|
||||
# test imports
|
||||
@ -43,7 +46,7 @@ def test_handle_menu(activated_account,
|
||||
ussd_menu = UssdMenu.find_by_name('exit_pin_blocked')
|
||||
assert menu_resp.get('name') == ussd_menu.get('name')
|
||||
menu_resp = handle_menu(pending_account, init_database)
|
||||
ussd_menu = UssdMenu.find_by_name('initial_language_selection')
|
||||
ussd_menu = UssdMenu.find_by_name('initial_pin_entry')
|
||||
assert menu_resp.get('name') == ussd_menu.get('name')
|
||||
identifier = bytes.fromhex(strip_0x(pending_account.blockchain_address))
|
||||
key = cache_data_key(identifier, MetadataPointer.PREFERENCES)
|
||||
@ -75,38 +78,62 @@ def test_get_menu(activated_account,
|
||||
assert menu_resp.get('name') == ussd_menu.get('name')
|
||||
|
||||
|
||||
def test_handle_menu_operations(activated_account,
|
||||
cache_preferences,
|
||||
celery_session_worker,
|
||||
generic_ussd_session,
|
||||
init_database,
|
||||
def test_handle_no_account_menu_operations(celery_session_worker,
|
||||
init_cache,
|
||||
init_database,
|
||||
load_chain_spec,
|
||||
load_config,
|
||||
load_languages,
|
||||
load_ussd_menu,
|
||||
mock_account_creation_task_result,
|
||||
pending_account,
|
||||
persisted_ussd_session,
|
||||
person_metadata,
|
||||
set_locale_files,
|
||||
setup_metadata_request_handler,
|
||||
setup_metadata_signer,
|
||||
task_uuid):
|
||||
# sourcery skip: extract-duplicate-method
|
||||
chain_str = Chain.spec.__str__()
|
||||
initial_language_selection = 'ussd.initial_language_selection'
|
||||
phone = phone_number()
|
||||
external_session_id = os.urandom(20).hex()
|
||||
valid_service_codes = load_config.get('USSD_SERVICE_CODE').split(",")
|
||||
preferred_language = i18n.config.get('fallback')
|
||||
resp = handle_menu_operations(chain_str, external_session_id, phone, None, valid_service_codes[0], init_database, '4444')
|
||||
assert resp == translation_for('ussd.account_creation_prompt', preferred_language)
|
||||
key = cache_data_key('system:languages'.encode('utf-8'), MetadataPointer.NONE)
|
||||
cached_system_languages = get_cached_data(key)
|
||||
language_list: list = json.loads(cached_system_languages)
|
||||
fallback = translation_for('helpers.no_language_list', preferred_language)
|
||||
language_list_sets = ussd_menu_list(fallback=fallback, menu_list=language_list, split=3)
|
||||
resp = handle_menu_operations(external_session_id, phone, None, valid_service_codes[0], init_database, '')
|
||||
assert resp == translation_for(initial_language_selection, preferred_language,
|
||||
first_language_set=language_list_sets[0])
|
||||
cached_ussd_session = get_cached_data(external_session_id)
|
||||
ussd_session = json.loads(cached_ussd_session)
|
||||
assert ussd_session['msisdn'] == phone
|
||||
persisted_ussd_session.external_session_id = external_session_id
|
||||
persisted_ussd_session.msisdn = phone
|
||||
persisted_ussd_session.state = initial_language_selection[5:]
|
||||
init_database.add(persisted_ussd_session)
|
||||
init_database.commit()
|
||||
account_creation_prompt = 'ussd.account_creation_prompt'
|
||||
user_input = '2'
|
||||
resp = handle_menu_operations(external_session_id, phone, None, valid_service_codes[0], init_database, user_input)
|
||||
preferred_language = preferred_langauge_from_selection(user_input)
|
||||
assert resp == translation_for(account_creation_prompt, preferred_language)
|
||||
task_tracker = init_database.query(TaskTracker).filter_by(task_uuid=task_uuid).first()
|
||||
assert task_tracker.task_uuid == task_uuid
|
||||
cached_creation_task_uuid = get_cached_data(task_uuid)
|
||||
creation_task_uuid_data = json.loads(cached_creation_task_uuid)
|
||||
assert creation_task_uuid_data['status'] == 'PENDING'
|
||||
|
||||
|
||||
def test_handle_account_menu_operations(activated_account,
|
||||
cache_preferences,
|
||||
celery_session_worker,
|
||||
init_database,
|
||||
load_config,
|
||||
persisted_ussd_session,
|
||||
person_metadata,
|
||||
set_locale_files,
|
||||
setup_metadata_request_handler,
|
||||
setup_metadata_signer, ):
|
||||
valid_service_codes = load_config.get('USSD_SERVICE_CODE').split(",")
|
||||
identifier = bytes.fromhex(strip_0x(activated_account.blockchain_address))
|
||||
person_metadata_client = PersonMetadata(identifier)
|
||||
with requests_mock.Mocker(real_http=False) as request_mocker:
|
||||
@ -117,6 +144,5 @@ def test_handle_menu_operations(activated_account,
|
||||
phone = activated_account.phone_number
|
||||
preferred_language = get_cached_preferred_language(activated_account.blockchain_address)
|
||||
persisted_ussd_session.state = 'enter_transaction_recipient'
|
||||
resp = handle_menu_operations(chain_str, external_session_id, phone, None, valid_service_codes[0], init_database, '1')
|
||||
resp = handle_menu_operations(external_session_id, phone, None, valid_service_codes[0], init_database, '1')
|
||||
assert resp == translation_for('ussd.enter_transaction_recipient', preferred_language)
|
||||
|
||||
|
@ -10,7 +10,10 @@ from cic_types.models.person import get_contact_data_from_vcard
|
||||
# local imports
|
||||
from cic_ussd.account.metadata import get_cached_preferred_language
|
||||
from cic_ussd.metadata import PersonMetadata
|
||||
from cic_ussd.processor.util import latest_input, parse_person_metadata, resume_last_ussd_session
|
||||
from cic_ussd.processor.util import (latest_input,
|
||||
parse_person_metadata,
|
||||
resume_last_ussd_session,
|
||||
ussd_menu_list)
|
||||
from cic_ussd.translation import translation_for
|
||||
|
||||
|
||||
@ -60,3 +63,20 @@ def test_parse_person_metadata(activated_account, cache_person_metadata, cache_p
|
||||
])
|
||||
def test_resume_last_ussd_session(expected_menu_name, last_state, load_ussd_menu):
|
||||
assert resume_last_ussd_session(last_state).get('name') == expected_menu_name
|
||||
|
||||
|
||||
def test_ussd_menu_list(activated_account, cache_preferences, load_ussd_menu, set_locale_files):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
fallback = translation_for('helpers.no_transaction_history', preferred_language)
|
||||
menu_list_sets = ['1. FII 50.0', '2. GFT 60.0', '3. DET 49.99']
|
||||
split = 3
|
||||
menu_list = ussd_menu_list(fallback=fallback, menu_list=menu_list_sets, split=split)
|
||||
menu_list_sets = [menu_list_sets[item:item + split] for item in range(0, len(menu_list), split)]
|
||||
menu_list_reprs = []
|
||||
for i in range(split):
|
||||
try:
|
||||
menu_list_reprs.append(''.join(f'{list_set_item}\n' for list_set_item in menu_list_sets[i]).rstrip('\n'))
|
||||
except IndexError:
|
||||
menu_list_reprs.append(fallback)
|
||||
assert menu_list == menu_list_reprs
|
||||
|
@ -3,8 +3,7 @@ import json
|
||||
|
||||
# external imports
|
||||
import pytest
|
||||
import requests_mock
|
||||
from chainlib.hash import strip_0x
|
||||
|
||||
from cic_types.models.person import Person, get_contact_data_from_vcard
|
||||
|
||||
# local imports
|
||||
@ -12,9 +11,7 @@ from cic_ussd.cache import get_cached_data
|
||||
from cic_ussd.account.maps import gender
|
||||
from cic_ussd.account.metadata import get_cached_preferred_language
|
||||
from cic_ussd.db.enum import AccountStatus
|
||||
from cic_ussd.metadata import PreferencesMetadata
|
||||
from cic_ussd.state_machine.logic.account import (change_preferred_language,
|
||||
edit_user_metadata_attribute,
|
||||
from cic_ussd.state_machine.logic.account import (edit_user_metadata_attribute,
|
||||
parse_gender,
|
||||
parse_person_metadata,
|
||||
save_complete_person_metadata,
|
||||
@ -26,32 +23,6 @@ from cic_ussd.translation import translation_for
|
||||
# test imports
|
||||
|
||||
|
||||
@pytest.mark.parametrize('user_input, expected_preferred_language', [
|
||||
('1', 'en'),
|
||||
('2', 'sw')
|
||||
])
|
||||
def test_change_preferred_language(activated_account,
|
||||
celery_session_worker,
|
||||
expected_preferred_language,
|
||||
init_database,
|
||||
generic_ussd_session,
|
||||
mock_response,
|
||||
preferences,
|
||||
setup_metadata_request_handler,
|
||||
user_input):
|
||||
identifier = bytes.fromhex(strip_0x(activated_account.blockchain_address))
|
||||
preferences_metadata_client = PreferencesMetadata(identifier)
|
||||
with requests_mock.Mocker(real_http=False) as requests_mocker:
|
||||
requests_mocker.register_uri(
|
||||
'POST', preferences_metadata_client.url, status_code=200, reason='OK', json=mock_response
|
||||
)
|
||||
state_machine_data = (user_input, generic_ussd_session, activated_account, init_database)
|
||||
res = change_preferred_language(state_machine_data)
|
||||
init_database.commit()
|
||||
assert res.id is not None
|
||||
assert activated_account.preferred_language == expected_preferred_language
|
||||
|
||||
|
||||
@pytest.mark.parametrize('user_input', [
|
||||
'1',
|
||||
'2',
|
||||
|
@ -0,0 +1,52 @@
|
||||
# standard imports
|
||||
import json
|
||||
|
||||
# external imports
|
||||
import requests_mock
|
||||
from cic_types.condiments import MetadataPointer
|
||||
|
||||
# local imports
|
||||
from cic_ussd.cache import cache_data_key, get_cached_data
|
||||
from cic_ussd.metadata import PreferencesMetadata
|
||||
from cic_ussd.state_machine.logic.language import (change_preferred_language,
|
||||
is_valid_language_selection,
|
||||
preferred_langauge_from_selection,
|
||||
process_language_selection)
|
||||
|
||||
# test imports
|
||||
|
||||
|
||||
def test_change_preferred_language(activated_account,
|
||||
cached_ussd_session,
|
||||
celery_session_worker,
|
||||
init_database,
|
||||
load_languages,
|
||||
mocker,
|
||||
setup_metadata_signer,
|
||||
setup_metadata_request_handler):
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
preferences = {
|
||||
'preferred_language': 'en'
|
||||
}
|
||||
ussd_session['data'] = preferences
|
||||
mock_add_preferences_metadata = mocker.patch('cic_ussd.tasks.metadata.add_preferences_metadata.apply_async')
|
||||
with requests_mock.Mocker(real_http=False) as request_mocker:
|
||||
identifier = bytes.fromhex(activated_account.blockchain_address)
|
||||
metadata_client = PreferencesMetadata(identifier=identifier)
|
||||
request_mocker.register_uri('POST', metadata_client.url, status_code=201, reason='CREATED', json=preferences)
|
||||
state_machine_data = ('1', ussd_session, activated_account, init_database)
|
||||
change_preferred_language(state_machine_data)
|
||||
mock_add_preferences_metadata.assert_called_with(
|
||||
(activated_account.blockchain_address, preferences), {}, queue='cic-ussd')
|
||||
|
||||
|
||||
def test_is_valid_language_selection(activated_account,
|
||||
generic_ussd_session,
|
||||
init_cache,
|
||||
init_database,
|
||||
load_languages):
|
||||
state_machine_data = ('1', generic_ussd_session, activated_account, init_database)
|
||||
assert is_valid_language_selection(state_machine_data) is True
|
||||
state_machine_data = ('12', generic_ussd_session, activated_account, init_database)
|
||||
assert is_valid_language_selection(state_machine_data) is False
|
@ -9,7 +9,10 @@ from cic_ussd.state_machine.logic.menu import (menu_one_selected,
|
||||
menu_four_selected,
|
||||
menu_five_selected,
|
||||
menu_six_selected,
|
||||
menu_nine_selected,
|
||||
menu_zero_zero_selected,
|
||||
menu_eleven_selected,
|
||||
menu_twenty_two_selected,
|
||||
menu_ninety_nine_selected)
|
||||
|
||||
# test imports
|
||||
@ -29,8 +32,14 @@ def test_menu_selection(init_database, pending_account, persisted_ussd_session):
|
||||
assert menu_five_selected(('e', ussd_session, pending_account, init_database)) is False
|
||||
assert menu_six_selected(('6', ussd_session, pending_account, init_database)) is True
|
||||
assert menu_six_selected(('8', ussd_session, pending_account, init_database)) is False
|
||||
assert menu_nine_selected(('9', ussd_session, pending_account, init_database)) is True
|
||||
assert menu_nine_selected(('-', ussd_session, pending_account, init_database)) is False
|
||||
assert menu_zero_zero_selected(('00', ussd_session, pending_account, init_database)) is True
|
||||
assert menu_zero_zero_selected(('/', ussd_session, pending_account, init_database)) is False
|
||||
assert menu_eleven_selected(('11', ussd_session, pending_account, init_database)) is True
|
||||
assert menu_eleven_selected(('*', ussd_session, pending_account, init_database)) is False
|
||||
assert menu_twenty_two_selected(('22', ussd_session, pending_account, init_database)) is True
|
||||
assert menu_twenty_two_selected(('5', ussd_session, pending_account, init_database)) is False
|
||||
assert menu_ninety_nine_selected(('99', ussd_session, pending_account, init_database)) is True
|
||||
assert menu_ninety_nine_selected(('d', ussd_session, pending_account, init_database)) is False
|
||||
|
||||
|
@ -0,0 +1,221 @@
|
||||
# standard imports
|
||||
import json
|
||||
|
||||
# external imports
|
||||
import requests_mock
|
||||
|
||||
# local imports
|
||||
from cic_ussd.account.guardianship import Guardianship
|
||||
from cic_ussd.account.metadata import get_cached_preferred_language
|
||||
from cic_ussd.cache import cache_data_key, get_cached_data
|
||||
from cic_ussd.db.models.account import Account
|
||||
from cic_ussd.metadata import PersonMetadata
|
||||
from cic_ussd.state_machine.logic.pin_guard import (add_pin_guardian,
|
||||
is_dialers_pin_guardian,
|
||||
is_others_pin_guardian,
|
||||
is_set_pin_guardian,
|
||||
remove_pin_guardian,
|
||||
initiate_pin_reset,
|
||||
save_guardian_to_session_data,
|
||||
save_guarded_account_session_data,
|
||||
retrieve_person_metadata,
|
||||
is_valid_guardian_addition)
|
||||
from cic_ussd.translation import translation_for
|
||||
|
||||
|
||||
def test_save_guardian_to_session_data(activated_account,
|
||||
cached_ussd_session,
|
||||
celery_session_worker,
|
||||
guardian_account,
|
||||
init_cache,
|
||||
init_database):
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
ussd_session['msisdn'] = activated_account.phone_number
|
||||
state_machine_data = (guardian_account.phone_number, ussd_session, activated_account, init_database)
|
||||
save_guardian_to_session_data(state_machine_data)
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
assert ussd_session.get('data').get('guardian_phone_number') == guardian_account.phone_number
|
||||
|
||||
|
||||
def test_save_guarded_account_session_data(activated_account,
|
||||
cached_ussd_session,
|
||||
celery_session_worker,
|
||||
guardian_account,
|
||||
init_cache,
|
||||
init_database):
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
ussd_session['msisdn'] = guardian_account.phone_number
|
||||
state_machine_data = (activated_account.phone_number, ussd_session, guardian_account, init_database)
|
||||
save_guarded_account_session_data(state_machine_data)
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
assert ussd_session.get('data').get('guarded_account_phone_number') == activated_account.phone_number
|
||||
|
||||
|
||||
def test_retrieve_person_metadata(activated_account,
|
||||
cached_ussd_session,
|
||||
celery_session_worker,
|
||||
guardian_account,
|
||||
init_cache,
|
||||
init_database,
|
||||
mocker,
|
||||
person_metadata,
|
||||
setup_metadata_request_handler,
|
||||
setup_metadata_signer):
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
ussd_session['msisdn'] = activated_account.phone_number
|
||||
state_machine_data = (guardian_account.phone_number, ussd_session, activated_account, init_database)
|
||||
mocker_query_person_metadata = mocker.patch('cic_ussd.tasks.metadata.query_person_metadata.apply_async')
|
||||
with requests_mock.Mocker(real_http=False) as request_mocker:
|
||||
identifier = bytes.fromhex(activated_account.blockchain_address)
|
||||
metadata_client = PersonMetadata(identifier)
|
||||
request_mocker.register_uri('GET', metadata_client.url, json=person_metadata, reason='OK', status_code=200)
|
||||
retrieve_person_metadata(state_machine_data)
|
||||
mocker_query_person_metadata.assert_called_with((guardian_account.blockchain_address,), {}, queue='cic-ussd')
|
||||
|
||||
|
||||
def test_is_valid_guardian_addition(activated_account,
|
||||
cache_preferences,
|
||||
cached_ussd_session,
|
||||
celery_session_worker,
|
||||
init_cache,
|
||||
init_database,
|
||||
guardian_account,
|
||||
load_languages,
|
||||
load_ussd_menu,
|
||||
set_locale_files,
|
||||
setup_guardianship):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
state_machine_data = (guardian_account.phone_number, ussd_session, activated_account, init_database)
|
||||
assert is_valid_guardian_addition(state_machine_data) is True
|
||||
|
||||
state_machine_data = (activated_account.phone_number, ussd_session, activated_account, init_database)
|
||||
assert is_valid_guardian_addition(state_machine_data) is False
|
||||
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
failure_reason = translation_for('helpers.error.is_initiator', preferred_language)
|
||||
assert ussd_session.get('data').get('failure_reason') == failure_reason
|
||||
|
||||
state_machine_data = (Guardianship.guardians[0], ussd_session, activated_account, init_database)
|
||||
assert is_valid_guardian_addition(state_machine_data) is False
|
||||
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
failure_reason = translation_for('helpers.error.is_existent_guardian', preferred_language)
|
||||
assert ussd_session.get('data').get('failure_reason') == failure_reason
|
||||
|
||||
|
||||
def test_add_pin_guardian(activated_account, generic_ussd_session, guardian_account, init_database):
|
||||
generic_ussd_session['data'] = {'guardian_phone_number': guardian_account.phone_number}
|
||||
state_machine_data = ('', generic_ussd_session, activated_account, init_database)
|
||||
add_pin_guardian(state_machine_data)
|
||||
account = Account.get_by_phone_number(activated_account.phone_number, init_database)
|
||||
assert account.get_guardians()[0] == guardian_account.phone_number
|
||||
|
||||
|
||||
def test_is_set_pin_guardian(activated_account,
|
||||
cache_preferences,
|
||||
cached_ussd_session,
|
||||
celery_session_worker,
|
||||
init_cache,
|
||||
init_database,
|
||||
guardian_account,
|
||||
load_languages,
|
||||
load_ussd_menu,
|
||||
set_locale_files,
|
||||
setup_guardianship):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
assert is_set_pin_guardian(activated_account, guardian_account.phone_number, preferred_language, init_database,
|
||||
ussd_session) is False
|
||||
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
failure_reason = translation_for('helpers.error.is_not_existent_guardian', preferred_language)
|
||||
assert ussd_session.get('data').get('failure_reason') == failure_reason
|
||||
|
||||
assert is_set_pin_guardian(activated_account, Guardianship.guardians[0], preferred_language, init_database,
|
||||
ussd_session) is True
|
||||
|
||||
assert is_set_pin_guardian(activated_account, activated_account.phone_number, preferred_language, init_database,
|
||||
ussd_session) is False
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
failure_reason = translation_for('helpers.error.is_initiator', preferred_language)
|
||||
assert ussd_session.get('data').get('failure_reason') == failure_reason
|
||||
|
||||
|
||||
def test_is_dialers_pin_guardian(activated_account,
|
||||
cache_preferences,
|
||||
cached_ussd_session,
|
||||
celery_session_worker,
|
||||
init_database,
|
||||
guardian_account):
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
state_machine_data = (guardian_account.phone_number, ussd_session, activated_account, init_database)
|
||||
assert is_dialers_pin_guardian(state_machine_data) is False
|
||||
activated_account.add_guardian(guardian_account.phone_number)
|
||||
init_database.flush()
|
||||
state_machine_data = (guardian_account.phone_number, ussd_session, activated_account, init_database)
|
||||
assert is_dialers_pin_guardian(state_machine_data) is True
|
||||
|
||||
|
||||
def test_is_others_pin_guardian(activated_account,
|
||||
cache_preferences,
|
||||
cached_ussd_session,
|
||||
celery_session_worker,
|
||||
init_database,
|
||||
guardian_account):
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
state_machine_data = (activated_account.phone_number, ussd_session, guardian_account, init_database)
|
||||
assert is_others_pin_guardian(state_machine_data) is False
|
||||
activated_account.add_guardian(guardian_account.phone_number)
|
||||
init_database.flush()
|
||||
state_machine_data = (activated_account.phone_number, ussd_session, guardian_account, init_database)
|
||||
assert is_others_pin_guardian(state_machine_data) is True
|
||||
|
||||
|
||||
def test_remove_pin_guardian(activated_account, generic_ussd_session, guardian_account, init_database):
|
||||
generic_ussd_session['data'] = {'guardian_phone_number': guardian_account.phone_number}
|
||||
activated_account.add_guardian(guardian_account.phone_number)
|
||||
init_database.flush()
|
||||
assert activated_account.get_guardians()[0] == guardian_account.phone_number
|
||||
state_machine_data = ('', generic_ussd_session, activated_account, init_database)
|
||||
remove_pin_guardian(state_machine_data)
|
||||
assert len(activated_account.get_guardians()) == 0
|
||||
|
||||
|
||||
def test_initiate_pin_reset(activated_account,
|
||||
cache_preferences,
|
||||
celery_session_worker,
|
||||
cached_ussd_session,
|
||||
guardian_account,
|
||||
init_cache,
|
||||
init_database,
|
||||
load_ussd_menu,
|
||||
mock_notifier_api,
|
||||
set_locale_files):
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
ussd_session['data'] = {'guarded_account_phone_number': activated_account.phone_number}
|
||||
state_machine_data = ('', ussd_session, guardian_account, init_database)
|
||||
initiate_pin_reset(state_machine_data)
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
preferred_language = get_cached_preferred_language(blockchain_address)
|
||||
message = translation_for('sms.pin_reset_initiated', preferred_language, pin_initiator=guardian_account.standard_metadata_id())
|
||||
assert mock_notifier_api.get('message') == message
|
||||
assert mock_notifier_api.get('recipient') == activated_account.phone_number
|
||||
|
@ -23,6 +23,7 @@ def test_upsell_unregistered_recipient(activated_account,
|
||||
load_support_phone,
|
||||
mock_notifier_api,
|
||||
set_locale_files,
|
||||
set_active_token,
|
||||
valid_recipient):
|
||||
cached_ussd_session.set_data('recipient_phone_number', valid_recipient.phone_number)
|
||||
state_machine_data = ('', cached_ussd_session.to_json(), activated_account, init_database)
|
||||
|
@ -0,0 +1,69 @@
|
||||
# standard imports
|
||||
import json
|
||||
|
||||
# external imports
|
||||
from cic_types.condiments import MetadataPointer
|
||||
|
||||
# local imports
|
||||
from cic_ussd.cache import cache_data_key, get_cached_data
|
||||
from cic_ussd.state_machine.logic.tokens import (is_valid_token_selection,
|
||||
process_token_selection,
|
||||
set_selected_active_token)
|
||||
from cic_ussd.account.tokens import get_cached_token_data_list
|
||||
|
||||
|
||||
# test imports
|
||||
|
||||
|
||||
def test_is_valid_token_selection(activated_account,
|
||||
cache_token_data_list,
|
||||
cache_token_symbol_list,
|
||||
cached_ussd_session,
|
||||
init_cache,
|
||||
init_database):
|
||||
cached_token_data_list = get_cached_token_data_list(activated_account.blockchain_address)
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
ussd_session['data'] = {'account_tokens_list': cached_token_data_list}
|
||||
state_machine_data = ('GFT', ussd_session, activated_account, init_database)
|
||||
assert is_valid_token_selection(state_machine_data) is True
|
||||
state_machine_data = ('1', ussd_session, activated_account, init_database)
|
||||
assert is_valid_token_selection(state_machine_data) is True
|
||||
state_machine_data = ('3', ussd_session, activated_account, init_database)
|
||||
assert is_valid_token_selection(state_machine_data) is False
|
||||
|
||||
|
||||
def test_process_token_selection(activated_account,
|
||||
cache_token_data_list,
|
||||
cache_token_symbol_list,
|
||||
cached_ussd_session,
|
||||
celery_session_worker,
|
||||
init_cache,
|
||||
init_database):
|
||||
cached_token_data_list = get_cached_token_data_list(activated_account.blockchain_address)
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
ussd_session['data'] = {'account_tokens_list': cached_token_data_list}
|
||||
state_machine_data = ('GFT', ussd_session, activated_account, init_database)
|
||||
process_token_selection(state_machine_data)
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
assert ussd_session.get('data').get('selected_token').get('symbol') == 'GFT'
|
||||
|
||||
|
||||
def test_set_selected_active_token(activated_account,
|
||||
cache_token_data_list,
|
||||
cache_token_symbol_list,
|
||||
cached_ussd_session,
|
||||
init_cache,
|
||||
init_database):
|
||||
cached_token_data_list = get_cached_token_data_list(activated_account.blockchain_address)
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
ussd_session = json.loads(ussd_session)
|
||||
ussd_session['data'] = {'selected_token': cached_token_data_list[0]}
|
||||
state_machine_data = ('GFT', ussd_session, activated_account, init_database)
|
||||
set_selected_active_token(state_machine_data)
|
||||
identifier = bytes.fromhex(activated_account.blockchain_address)
|
||||
key = cache_data_key(identifier=identifier, salt=MetadataPointer.TOKEN_ACTIVE)
|
||||
active_token = get_cached_data(key)
|
||||
assert active_token == 'GFT'
|
@ -3,13 +3,12 @@ import json
|
||||
|
||||
# external imports
|
||||
import pytest
|
||||
import requests_mock
|
||||
from chainlib.hash import strip_0x
|
||||
|
||||
# local imports
|
||||
from cic_ussd.account.metadata import get_cached_preferred_language
|
||||
from cic_ussd.account.tokens import get_active_token_symbol, get_cached_token_data
|
||||
from cic_ussd.account.transaction import to_wei
|
||||
from cic_ussd.cache import get_cached_data
|
||||
from cic_ussd.metadata import PersonMetadata
|
||||
from cic_ussd.state_machine.logic.transaction import (is_valid_recipient,
|
||||
is_valid_transaction_amount,
|
||||
has_sufficient_balance,
|
||||
@ -18,7 +17,6 @@ from cic_ussd.state_machine.logic.transaction import (is_valid_recipient,
|
||||
save_recipient_phone_to_session_data,
|
||||
save_transaction_amount_to_session_data)
|
||||
|
||||
|
||||
# test imports
|
||||
|
||||
|
||||
@ -49,17 +47,18 @@ def test_is_valid_transaction_amount(activated_account, amount, expected_result,
|
||||
])
|
||||
def test_has_sufficient_balance(activated_account,
|
||||
cache_balances,
|
||||
cache_default_token_data,
|
||||
cache_token_data,
|
||||
expected_result,
|
||||
generic_ussd_session,
|
||||
init_database,
|
||||
set_active_token,
|
||||
value):
|
||||
state_machine_data = (value, generic_ussd_session, activated_account, init_database)
|
||||
assert has_sufficient_balance(state_machine_data=state_machine_data) == expected_result
|
||||
|
||||
|
||||
def test_process_transaction_request(activated_account,
|
||||
cache_default_token_data,
|
||||
cache_token_data,
|
||||
cached_ussd_session,
|
||||
celery_session_worker,
|
||||
init_cache,
|
||||
@ -67,7 +66,12 @@ def test_process_transaction_request(activated_account,
|
||||
load_chain_spec,
|
||||
load_config,
|
||||
mock_transfer_api,
|
||||
set_active_token,
|
||||
valid_recipient):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
token_symbol = get_active_token_symbol(blockchain_address)
|
||||
token_data = get_cached_token_data(blockchain_address, token_symbol)
|
||||
decimals = token_data.get("decimals")
|
||||
cached_ussd_session.set_data('recipient_phone_number', valid_recipient.phone_number)
|
||||
cached_ussd_session.set_data('transaction_amount', '50')
|
||||
ussd_session = get_cached_data(cached_ussd_session.external_session_id)
|
||||
@ -76,7 +80,7 @@ def test_process_transaction_request(activated_account,
|
||||
process_transaction_request(state_machine_data)
|
||||
assert mock_transfer_api['from_address'] == activated_account.blockchain_address
|
||||
assert mock_transfer_api['to_address'] == valid_recipient.blockchain_address
|
||||
assert mock_transfer_api['value'] == to_wei(50)
|
||||
assert mock_transfer_api['value'] == to_wei(decimals, 50)
|
||||
assert mock_transfer_api['token_symbol'] == load_config.get('TEST_TOKEN_SYMBOL')
|
||||
|
||||
|
||||
|
@ -6,8 +6,10 @@ def test_state_machine(activated_account_ussd_session,
|
||||
celery_session_worker,
|
||||
init_database,
|
||||
init_state_machine,
|
||||
pending_account):
|
||||
load_languages,
|
||||
pending_account,
|
||||
set_locale_files):
|
||||
state_machine = UssdStateMachine(activated_account_ussd_session)
|
||||
state_machine.scan_data(('1', activated_account_ussd_session, pending_account, init_database))
|
||||
assert state_machine.__repr__() == f'<KenyaUssdStateMachine: {state_machine.state}>'
|
||||
assert state_machine.state == 'initial_pin_entry'
|
||||
assert state_machine.state == 'account_creation_prompt'
|
||||
|
@ -4,15 +4,18 @@ import json
|
||||
# external imports
|
||||
import celery
|
||||
import pytest
|
||||
import requests_mock
|
||||
from chainlib.hash import strip_0x
|
||||
from cic_types.condiments import MetadataPointer
|
||||
|
||||
# local imports
|
||||
from cic_ussd.account.statement import filter_statement_transactions
|
||||
from cic_ussd.account.tokens import collate_token_metadata
|
||||
from cic_ussd.account.transaction import transaction_actors
|
||||
from cic_ussd.cache import cache_data_key, get_cached_data
|
||||
from cic_ussd.db.models.account import Account
|
||||
from cic_ussd.error import AccountCreationDataNotFound
|
||||
from cic_ussd.metadata import TokenMetadata
|
||||
|
||||
|
||||
# test imports
|
||||
@ -22,11 +25,13 @@ from tests.helpers.accounts import blockchain_address
|
||||
def test_account_creation_callback(account_creation_data,
|
||||
cache_account_creation_data,
|
||||
celery_session_worker,
|
||||
cache_default_token_data,
|
||||
custom_metadata,
|
||||
init_cache,
|
||||
init_database,
|
||||
load_chain_spec,
|
||||
mocker,
|
||||
preferences,
|
||||
setup_metadata_request_handler,
|
||||
setup_metadata_signer):
|
||||
phone_number = account_creation_data.get('phone_number')
|
||||
@ -48,10 +53,12 @@ def test_account_creation_callback(account_creation_data,
|
||||
cached_account_creation_data = get_cached_data(task_uuid)
|
||||
cached_account_creation_data = json.loads(cached_account_creation_data)
|
||||
assert cached_account_creation_data.get('status') == account_creation_data.get('status')
|
||||
mock_add_preferences_metadata = mocker.patch('cic_ussd.tasks.metadata.add_preferences_metadata.apply_async')
|
||||
mock_add_phone_pointer = mocker.patch('cic_ussd.tasks.metadata.add_phone_pointer.apply_async')
|
||||
mock_add_custom_metadata = mocker.patch('cic_ussd.tasks.metadata.add_custom_metadata.apply_async')
|
||||
preferred_language = preferences.get('preferred_language')
|
||||
s_account_creation_callback = celery.signature(
|
||||
'cic_ussd.tasks.callback_handler.account_creation_callback', [result, '', 0]
|
||||
'cic_ussd.tasks.callback_handler.account_creation_callback', [result, preferred_language, 0]
|
||||
)
|
||||
s_account_creation_callback.apply_async().get()
|
||||
account = init_database.query(Account).filter_by(phone_number=phone_number).first()
|
||||
@ -59,6 +66,7 @@ def test_account_creation_callback(account_creation_data,
|
||||
cached_account_creation_data = get_cached_data(task_uuid)
|
||||
cached_account_creation_data = json.loads(cached_account_creation_data)
|
||||
assert cached_account_creation_data.get('status') == 'CREATED'
|
||||
mock_add_preferences_metadata.assert_called_with((result, preferences), {}, queue='cic-ussd')
|
||||
mock_add_phone_pointer.assert_called_with((result, phone_number), {}, queue='cic-ussd')
|
||||
mock_add_custom_metadata.assert_called_with((result, custom_metadata), {}, queue='cic-ussd')
|
||||
|
||||
@ -117,12 +125,46 @@ def test_statement_callback(activated_account, mocker, transactions_list):
|
||||
(activated_account.blockchain_address, sender_transaction), {}, queue='cic-ussd')
|
||||
|
||||
|
||||
def test_token_data_callback(activated_account,
|
||||
cache_token_data,
|
||||
cache_token_meta_symbol,
|
||||
cache_token_proof_symbol,
|
||||
celery_session_worker,
|
||||
default_token_data,
|
||||
init_cache,
|
||||
token_meta_symbol,
|
||||
token_symbol):
|
||||
blockchain_address = activated_account.blockchain_address
|
||||
identifier = token_symbol.encode('utf-8')
|
||||
status_code = 1
|
||||
with pytest.raises(ValueError) as error:
|
||||
s_token_data_callback = celery.signature(
|
||||
'cic_ussd.tasks.callback_handler.token_data_callback',
|
||||
[[default_token_data], blockchain_address, status_code])
|
||||
s_token_data_callback.apply_async().get()
|
||||
assert str(error.value) == f'Unexpected status code: {status_code}.'
|
||||
|
||||
token_data_key = cache_data_key([bytes.fromhex(blockchain_address), identifier], MetadataPointer.TOKEN_DATA)
|
||||
token_meta_key = cache_data_key(identifier, MetadataPointer.TOKEN_META_SYMBOL)
|
||||
token_info_key = cache_data_key(identifier, MetadataPointer.TOKEN_PROOF_SYMBOL)
|
||||
token_meta = get_cached_data(token_meta_key)
|
||||
token_meta = json.loads(token_meta)
|
||||
token_info = get_cached_data(token_info_key)
|
||||
token_info = json.loads(token_info)
|
||||
token_data = collate_token_metadata(token_info=token_info, token_metadata=token_meta)
|
||||
token_data = {**token_data, **default_token_data}
|
||||
cached_token_data = json.loads(get_cached_data(token_data_key))
|
||||
for key, value in token_data.items():
|
||||
assert token_data[key] == cached_token_data[key]
|
||||
|
||||
|
||||
def test_transaction_balances_callback(activated_account,
|
||||
balances,
|
||||
cache_balances,
|
||||
cache_default_token_data,
|
||||
cache_token_data,
|
||||
cache_person_metadata,
|
||||
cache_preferences,
|
||||
celery_session_worker,
|
||||
load_chain_spec,
|
||||
mocker,
|
||||
preferences,
|
||||
@ -157,7 +199,16 @@ def test_transaction_balances_callback(activated_account,
|
||||
mocked_chain.assert_called()
|
||||
|
||||
|
||||
def test_transaction_callback(load_chain_spec, mock_async_balance_api_query, transaction_result):
|
||||
def test_transaction_callback(cache_token_data,
|
||||
celery_session_worker,
|
||||
default_token_data,
|
||||
init_cache,
|
||||
load_chain_spec,
|
||||
mock_async_balance_api_query,
|
||||
token_symbol,
|
||||
token_meta_symbol,
|
||||
token_proof_symbol,
|
||||
transaction_result):
|
||||
status_code = 1
|
||||
with pytest.raises(ValueError) as error:
|
||||
s_transaction_callback = celery.signature(
|
||||
@ -166,6 +217,12 @@ def test_transaction_callback(load_chain_spec, mock_async_balance_api_query, tra
|
||||
s_transaction_callback.apply_async().get()
|
||||
assert str(error.value) == f'Unexpected status code: {status_code}.'
|
||||
|
||||
with requests_mock.Mocker(real_http=False) as request_mocker:
|
||||
identifier = token_symbol.encode('utf-8')
|
||||
metadata_client = TokenMetadata(identifier, cic_type=MetadataPointer.TOKEN_META_SYMBOL)
|
||||
request_mocker.register_uri('GET', metadata_client.url, json=token_meta_symbol, status_code=200, reason='OK')
|
||||
metadata_client = TokenMetadata(identifier, cic_type=MetadataPointer.TOKEN_PROOF_SYMBOL)
|
||||
request_mocker.register_uri('GET', metadata_client.url, json=token_proof_symbol, status_code=200, reason='OK')
|
||||
status_code = 0
|
||||
s_transaction_callback = celery.signature(
|
||||
'cic_ussd.tasks.callback_handler.transaction_callback',
|
||||
|
@ -14,13 +14,14 @@ from cic_ussd.translation import translation_for
|
||||
|
||||
|
||||
def test_transaction(cache_default_token_data,
|
||||
cache_token_data,
|
||||
celery_session_worker,
|
||||
load_support_phone,
|
||||
mock_notifier_api,
|
||||
notification_data,
|
||||
set_locale_files):
|
||||
notification_data['transaction_type'] = 'transfer'
|
||||
amount = from_wei(notification_data.get('token_value'))
|
||||
amount = from_wei(6, notification_data.get('token_value'))
|
||||
balance = notification_data.get('available_balance')
|
||||
phone_number = notification_data.get('phone_number')
|
||||
preferred_language = notification_data.get('preferred_language')
|
||||
|
@ -52,6 +52,11 @@ def test_cache_statement(activated_account,
|
||||
cached_statement = get_cached_data(key)
|
||||
cached_statement = json.loads(cached_statement)
|
||||
assert len(cached_statement) == 1
|
||||
|
||||
sender_transaction['token_value'] = 60.0
|
||||
s_parse_transaction = celery.signature(
|
||||
'cic_ussd.tasks.processor.parse_transaction', [sender_transaction])
|
||||
result = s_parse_transaction.apply_async().get()
|
||||
s_cache_statement = celery.signature(
|
||||
'cic_ussd.tasks.processor.cache_statement', [result, activated_account.blockchain_address]
|
||||
)
|
||||
|
@ -9,7 +9,7 @@ from cic_ussd.notifications import Notifier
|
||||
|
||||
@pytest.mark.parametrize("key, preferred_language, recipient, expected_message", [
|
||||
("ussd.exit", "en", "+254712345678", "END Thank you for using the service."),
|
||||
("ussd.exit", "sw", "+254712345678", "END Asante kwa kutumia huduma.")
|
||||
("ussd.exit", "sw", "+254712345678", "END Asante kwa kutumia huduma")
|
||||
])
|
||||
def test_send_sms_notification(celery_session_worker,
|
||||
expected_message,
|
||||
|
@ -17,5 +17,5 @@ def test_translation_for(set_locale_files):
|
||||
key='ussd.exit_invalid_request',
|
||||
preferred_language='sw'
|
||||
)
|
||||
assert swahili_translation == 'END Chaguo si sahihi.'
|
||||
assert swahili_translation == 'END Chaguo si sahihi'
|
||||
assert english_translation == 'END Invalid request.'
|
||||
|
160
apps/cic-ussd/tests/fixtures/account.py
vendored
160
apps/cic-ussd/tests/fixtures/account.py
vendored
@ -8,6 +8,7 @@ from cic_types.condiments import MetadataPointer
|
||||
|
||||
# local imports
|
||||
from cic_ussd.account.chain import Chain
|
||||
from cic_ussd.account.tokens import set_active_token
|
||||
from cic_ussd.cache import cache_data, cache_data_key
|
||||
from cic_ussd.db.enum import AccountStatus
|
||||
from cic_ussd.db.models.account import Account
|
||||
@ -36,6 +37,16 @@ def activated_account(init_database, set_fernet_key):
|
||||
return account
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def guardian_account(init_database, set_fernet_key):
|
||||
account = Account(blockchain_address(), phone_number())
|
||||
account.create_password('0000')
|
||||
account.activate_account()
|
||||
init_database.add(account)
|
||||
init_database.commit()
|
||||
return account
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def balances():
|
||||
return [{
|
||||
@ -53,13 +64,22 @@ def cache_account_creation_data(init_cache, account_creation_data):
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def cache_balances(activated_account, balances, init_cache):
|
||||
identifier = bytes.fromhex(activated_account.blockchain_address)
|
||||
def cache_balances(activated_account, balances, init_cache, token_symbol):
|
||||
identifier = [bytes.fromhex(activated_account.blockchain_address), token_symbol.encode('utf-8')]
|
||||
balances = json.dumps(balances[0])
|
||||
key = cache_data_key(identifier, MetadataPointer.BALANCES)
|
||||
cache_data(key, balances)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def cache_adjusted_balances(activated_account, balances, init_cache, token_symbol):
|
||||
identifier = bytes.fromhex(activated_account.blockchain_address)
|
||||
balances_identifier = [identifier, token_symbol.encode('utf-8')]
|
||||
key = cache_data_key(balances_identifier, MetadataPointer.BALANCES_ADJUSTED)
|
||||
adjusted_balance = 45931650.64654012
|
||||
cache_data(key, adjusted_balance)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def cache_default_token_data(default_token_data, init_cache, load_chain_spec):
|
||||
chain_str = Chain.spec.__str__()
|
||||
@ -68,6 +88,113 @@ def cache_default_token_data(default_token_data, init_cache, load_chain_spec):
|
||||
cache_data(key, data)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def set_active_token(activated_account, init_cache, token_symbol):
|
||||
identifier = bytes.fromhex(activated_account.blockchain_address)
|
||||
key = cache_data_key(identifier, MetadataPointer.TOKEN_ACTIVE)
|
||||
cache_data(key=key, data=token_symbol)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def cache_token_data(activated_account, init_cache, token_data):
|
||||
identifier = [bytes.fromhex(activated_account.blockchain_address), token_data.get('symbol').encode('utf-8')]
|
||||
key = cache_data_key(identifier, MetadataPointer.TOKEN_DATA)
|
||||
cache_data(key=key, data=json.dumps(token_data))
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def cache_token_symbol_list(activated_account, init_cache, token_symbol):
|
||||
identifier = bytes.fromhex(activated_account.blockchain_address)
|
||||
key = cache_data_key(identifier=identifier, salt=MetadataPointer.TOKEN_SYMBOLS_LIST)
|
||||
token_symbols_list = [token_symbol]
|
||||
cache_data(key, json.dumps(token_symbols_list))
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def cache_token_data_list(activated_account, init_cache, token_data):
|
||||
identifier = bytes.fromhex(activated_account.blockchain_address)
|
||||
key = cache_data_key(identifier, MetadataPointer.TOKEN_DATA_LIST)
|
||||
token_data_list = [token_data]
|
||||
cache_data(key, json.dumps(token_data_list))
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def token_meta_symbol():
|
||||
return {
|
||||
"contact": {
|
||||
"phone": "+254700000000",
|
||||
"email": "info@grassrootseconomics.org"
|
||||
},
|
||||
"country_code": "KE",
|
||||
"location": "Kilifi",
|
||||
"name": "GRASSROOTS ECONOMICS"
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def token_proof_symbol():
|
||||
return {
|
||||
"description": "Community support",
|
||||
"issuer": "Grassroots Economics",
|
||||
"namespace": "ge",
|
||||
"proofs": [
|
||||
"0x4746540000000000000000000000000000000000000000000000000000000000",
|
||||
"1f0f0e3e9db80eeaba22a9d4598e454be885855d6048545546fd488bb709dc2f"
|
||||
],
|
||||
"version": 0
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def token_list_entries():
|
||||
return [
|
||||
{
|
||||
'name': 'Fee',
|
||||
'symbol': 'FII',
|
||||
'issuer': 'Foo',
|
||||
'contact': {'phone': '+254712345678'},
|
||||
'location': 'Fum',
|
||||
'balance': 50.0
|
||||
},
|
||||
{
|
||||
'name': 'Giftable Token',
|
||||
'symbol': 'GFT',
|
||||
'issuer': 'Grassroots Economics',
|
||||
'contact': {
|
||||
'phone': '+254700000000',
|
||||
'email': 'info@grassrootseconomics.org'
|
||||
},
|
||||
'location': 'Fum',
|
||||
'balance': 60.0
|
||||
},
|
||||
{
|
||||
'name': 'Demurrage Token',
|
||||
'symbol': 'DET',
|
||||
'issuer': 'Grassroots Economics',
|
||||
'contact': {
|
||||
'phone': '+254700000000',
|
||||
'email': 'info@grassrootseconomics.org'
|
||||
},
|
||||
'location': 'Fum',
|
||||
'balance': 49.99
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def cache_token_meta_symbol(token_meta_symbol, token_symbol):
|
||||
identifier = token_symbol.encode('utf-8')
|
||||
key = cache_data_key(identifier, MetadataPointer.TOKEN_META_SYMBOL)
|
||||
cache_data(key, json.dumps(token_meta_symbol))
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def cache_token_proof_symbol(token_proof_symbol, token_symbol):
|
||||
identifier = token_symbol.encode('utf-8')
|
||||
key = cache_data_key(identifier, MetadataPointer.TOKEN_PROOF_SYMBOL)
|
||||
cache_data(key, json.dumps(token_proof_symbol))
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def cache_person_metadata(activated_account, init_cache, person_metadata):
|
||||
identifier = bytes.fromhex(activated_account.blockchain_address)
|
||||
@ -101,9 +228,32 @@ def custom_metadata():
|
||||
def default_token_data(token_symbol):
|
||||
return {
|
||||
'symbol': token_symbol,
|
||||
'address': blockchain_address(),
|
||||
'name': 'Giftable',
|
||||
'decimals': 6
|
||||
'address': '32e860c2a0645d1b7b005273696905f5d6dc5d05',
|
||||
'name': 'Giftable Token',
|
||||
'decimals': 6,
|
||||
"converters": []
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def token_data():
|
||||
return {
|
||||
"description": "Community support",
|
||||
"issuer": "Grassroots Economics",
|
||||
"location": "Kilifi",
|
||||
"contact": {
|
||||
"phone": "+254700000000",
|
||||
"email": "info@grassrootseconomics.org"
|
||||
},
|
||||
"decimals": 6,
|
||||
"name": "Giftable Token",
|
||||
"symbol": "GFT",
|
||||
"address": "32e860c2a0645d1b7b005273696905f5d6dc5d05",
|
||||
"proofs": [
|
||||
"0x4746540000000000000000000000000000000000000000000000000000000000",
|
||||
"1f0f0e3e9db80eeaba22a9d4598e454be885855d6048545546fd488bb709dc2f"
|
||||
],
|
||||
"converters": []
|
||||
}
|
||||
|
||||
|
||||
|
12
apps/cic-ussd/tests/fixtures/cache.py
vendored
12
apps/cic-ussd/tests/fixtures/cache.py
vendored
@ -2,14 +2,18 @@
|
||||
|
||||
# external imports
|
||||
import pytest
|
||||
from pytest_redis import factories
|
||||
|
||||
# local imports
|
||||
from cic_ussd.cache import Cache
|
||||
from cic_ussd.session.ussd_session import UssdSession
|
||||
|
||||
redis_test_proc = factories.redis_proc()
|
||||
redis_db = factories.redisdb('redis_test_proc', decode=True)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def init_cache(redisdb):
|
||||
Cache.store = redisdb
|
||||
UssdSession.store = redisdb
|
||||
return redisdb
|
||||
def init_cache(redis_db):
|
||||
Cache.store = redis_db
|
||||
UssdSession.store = redis_db
|
||||
return redis_db
|
||||
|
33
apps/cic-ussd/tests/fixtures/config.py
vendored
33
apps/cic-ussd/tests/fixtures/config.py
vendored
@ -10,11 +10,13 @@ from confini import Config
|
||||
|
||||
# local imports
|
||||
from cic_ussd.account.chain import Chain
|
||||
from cic_ussd.account.guardianship import Guardianship
|
||||
from cic_ussd.encoder import PasswordEncoder
|
||||
from cic_ussd.files.local_files import create_local_file_data_stores, json_file_parser
|
||||
from cic_ussd.menu.ussd_menu import UssdMenu
|
||||
from cic_ussd.phone_number import E164Format, Support
|
||||
from cic_ussd.state_machine import UssdStateMachine
|
||||
from cic_ussd.translation import generate_locale_files, Languages
|
||||
from cic_ussd.validator import validate_presence
|
||||
|
||||
logg = logging.getLogger(__name__)
|
||||
@ -39,6 +41,14 @@ def init_state_machine(load_config):
|
||||
UssdStateMachine.transitions = json_file_parser(filepath=load_config.get('MACHINE_TRANSITIONS'))
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def load_languages(init_cache, load_config):
|
||||
validate_presence(load_config.get('LANGUAGES_FILE'))
|
||||
Languages.load_languages_dict(load_config.get('LANGUAGES_FILE'))
|
||||
languages = Languages()
|
||||
languages.cache_system_languages()
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def load_chain_spec(load_config):
|
||||
chain_spec = ChainSpec.from_chain_str(load_config.get('CHAIN_SPEC'))
|
||||
@ -75,8 +85,23 @@ def set_fernet_key(load_config):
|
||||
PasswordEncoder.set_key(load_config.get('APP_PASSWORD_PEPPER'))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def set_locale_files(load_config):
|
||||
validate_presence(load_config.get('LOCALE_PATH'))
|
||||
i18n.load_path.append(load_config.get('LOCALE_PATH'))
|
||||
@pytest.fixture(scope='function')
|
||||
def setup_guardianship(load_config):
|
||||
guardians_file = os.path.join(root_directory, load_config.get('SYSTEM_GUARDIANS_FILE'))
|
||||
validate_presence(guardians_file)
|
||||
Guardianship.load_system_guardians(guardians_file)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def set_locale_files(load_config, tmpdir_factory):
|
||||
tmpdir = tmpdir_factory.mktemp("var")
|
||||
tmpdir_path = str(tmpdir)
|
||||
validate_presence(tmpdir_path)
|
||||
import cic_translations
|
||||
package_path = cic_translations.__path__
|
||||
schema_files = os.path.join(package_path[0], load_config.get("SCHEMA_FILE_PATH"))
|
||||
generate_locale_files(locale_dir=tmpdir_path,
|
||||
schema_file_path=schema_files,
|
||||
translation_builder_path=load_config.get('LOCALE_FILE_BUILDERS'))
|
||||
i18n.load_path.append(tmpdir_path)
|
||||
i18n.set('fallback', load_config.get('LOCALE_FALLBACK'))
|
||||
|
3
apps/cic-ussd/tests/fixtures/transaction.py
vendored
3
apps/cic-ussd/tests/fixtures/transaction.py
vendored
@ -40,6 +40,7 @@ def statement(activated_account):
|
||||
'blockchain_address': activated_account.blockchain_address,
|
||||
'token_symbol': 'GFT',
|
||||
'token_value': 25000000,
|
||||
'token_decimals': 6,
|
||||
'role': 'sender',
|
||||
'action_tag': 'Sent',
|
||||
'direction_tag': 'To',
|
||||
@ -63,7 +64,7 @@ def transaction_result(activated_account, load_config, valid_recipient):
|
||||
'destination_token_symbol': load_config.get('TEST_TOKEN_SYMBOL'),
|
||||
'source_token_decimals': 6,
|
||||
'destination_token_decimals': 6,
|
||||
'chain': 'evm:bloxberg:8996'
|
||||
'chain': load_config.get('CHAIN_SPEC')
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,19 +0,0 @@
|
||||
keys,en,sw
|
||||
female,Female,Mwanamke
|
||||
from,From,Kutoka kwa
|
||||
male,Male,Mwanaume
|
||||
not_provided,Not provided,Haijawekwa
|
||||
no_language_list,No language list,Hamna lugha ya kuchagua
|
||||
no_transaction_history,No transaction history,Hamna ripoti ya matumizi
|
||||
no_tokens_list,No tokens to list,Hamna sarafu nyingine
|
||||
other,Other,Nyingine
|
||||
received,Received,Ulipokea
|
||||
sent,Sent,Ulituma
|
||||
to,To,Kwa
|
||||
guardians_list_header,Your set guardians are:,Walinzi uliowaongeza ni:
|
||||
no_guardians_list,No guardians set,Hamna walinzi walioongezwa
|
||||
error.no_phone_number_provided,No phone number was provided.,Namabari ya simu haijawekwa.
|
||||
error.no_matching_account,The number provided is not registered.,Nambari uliyoweka haijasajiliwa.
|
||||
error.is_initiator,Phone number cannot be your own.,Nambari yafaa kuwa tofauti na yako.
|
||||
error.is_existent_guardian,This phone number is is already added as a guardian.,Namabari hii tayari imeongezwa kama mlinzi wa nambari ya siri.
|
||||
error.is_not_existent_guardian,Phone number not set as PIN reset guardian.,Nambari hii haijaongezwa kama mlinzi wa nambari ya siri.
|
|
@ -1,7 +0,0 @@
|
||||
keys,en,sw
|
||||
account_successfully_created,You have been registered on Sarafu Network! To use dial *384*96# on Safaricom and *483*96# on other networks. For help %{support_phone}.,Umesajiliwa kwa huduma ya Sarafu! Kutumia bonyeza *384*96# Safaricom ama *483*46# kwa utandao tofauti. Kwa Usaidizi %{support_phone}.
|
||||
received_tokens,Successfully received %{amount} %{token_symbol} from %{tx_sender_information} %{timestamp} to %{tx_recipient_information}. New balance is %{balance} %{token_symbol}.,Umepokea %{amount} %{token_symbol} kutoka kwa %{tx_sender_information} %{timestamp} ikapokewa na %{tx_recipient_information}. Salio lako ni %{balance} %{token_symbol}.
|
||||
sent_tokens,Successfully sent %{amount} %{token_symbol} to %{tx_recipient_information} %{timestamp} from %{tx_sender_information}. New balance is %{balance} %{token_symbol}.,Umetuma %{amount} %{token_symbol} kwa %{tx_recipient_information} %{timestamp} kutoka kwa %{tx_sender_information}. Salio lako ni %{balance} %{token_symbol}.
|
||||
terms,"By using the service, you agree to the terms and conditions at http://grassecon.org/tos","Kwa kutumia hii huduma, umekubali sheria na masharti yafuatayo http://grassecon.org/tos"
|
||||
upsell_unregistered_recipient,%{tx_sender_information} tried to send you %{token_symbol} but you are not registered. To use dial *384*96# on Safaricom and *483*96# on other networks. For help %{support_phone}.,%{tx_sender_information} amejaribu kukutumia %{token_symbol} lakini hujasajili. Kutumia bonyeza *384*96# Safaricom ama *483*46# kwa utandao tofauti. Kwa Usaidizi %{support_phone}.
|
||||
pin_reset_initiated,%{pin_initiator} has sent a request to initiate your PIN reset.,%{pin_initiator} ametuma ombi la kubadilisha PIN yako.
|
|
@ -1,862 +0,0 @@
|
||||
keys,en,sw,kam,kik,miji,luo,bor
|
||||
initial_language_selection,"CON Welcome to Sarafu Network
|
||||
%{first_language_set}
|
||||
|
||||
11. Next
|
||||
00. Exit","CON Karibu Sarafu Network
|
||||
%{first_language_set}
|
||||
|
||||
11. Mbele
|
||||
00. Ondoka","CON Kalivu Network ya Sarafu
|
||||
1. English
|
||||
2. Kiswahili
|
||||
3. Kikamba
|
||||
3. Help","CON Karibu Sarafu Network
|
||||
1. Githungu
|
||||
2. Githweri
|
||||
3. Uteithio","CON Karibu Sarafu Network
|
||||
1. Chizungu
|
||||
2. Chiswahili
|
||||
3. Avizwa","CON Machiegni e network mar Sarafu
|
||||
1. Dho Ngere
|
||||
2. Dho oswayo
|
||||
3. Kony","CON Karibu Sarafu Network
|
||||
1. Afaan ferenji
|
||||
2. Afaan kiswahili
|
||||
3. Qarqars"
|
||||
initial_pin_entry,CON Please enter a new four number PIN for your account.,CON Tafadhali weka pin mpya yenye nambari nne kwa akaunti yako,CON Tafadhali ikia pin yumbya ila ina namba inya kinanduni chaku,CON Ekera namba yaku ya thiri njeru ena numba enna.,CON Ika piniyo ya namba Ne kwa akaunti Yakwako.,CON Kiyie to ket namba ni mopondo e akaont ni.,CON Tafadhal pin hareti kekhae ka namba afuri fulaa akaunti kake
|
||||
initial_pin_confirmation,CON Enter your four number PIN again,CON Weka PIN yako tena,CON Ikia PIN yaku ingi,CON Ekera namba yaku ya thiri ringi,CON Uyira Kwika pin kaheri.,CON Ket namba ni mopondo kendo,CON Mar dibii pin kekhae
|
||||
enter_given_name,"CON Enter first name
|
||||
0. Back",CON Weka jina lako la kwanza,CON Ikia isyitwa yaku ya mbee,CON Ekera retwa rwaku ria mbere,CON Ika dzinaro rakwanza.,CON Ket nyingi mokwongo,CON Makhaa kake ka karaa kor
|
||||
enter_family_name,"CON Enter family name
|
||||
0. Back","CON Weka jina lako la mwisho
|
||||
0. Rudi","CON Ikia isyitwa yaku ya muthya
|
||||
0. Syoka itina","CON Ekera ritwa rwaku ria mwisho
|
||||
0. Coka thutha","CON Ika dzinaro ra mwisho
|
||||
0. Uya Nyuma","CON Ket nyingi mogik.
|
||||
0. Dog chien","CON Makhaa kake ka egee
|
||||
0. Dhebii"
|
||||
enter_date_of_birth,"CON Enter year of birth
|
||||
0. Back","CON Weka mwaka wa kuzaliwa
|
||||
0. Rudi","CON Ikia mwaka wa kusyawa
|
||||
0. Syoka itina","CON Ekera mwaka waku wa guciarwo
|
||||
0. Coka thutha","CON Ika mwaka wakuvyalwa
|
||||
0. Uya Nyuma","CON Ket iki mar nyuol
|
||||
0. Dog chien","CON Gan kake ka athdalat kor
|
||||
0. Dheebi"
|
||||
enter_gender,"CON Enter gender
|
||||
1. Male
|
||||
2. Female
|
||||
3. Other
|
||||
0. Back","CON Weka jinsia yako
|
||||
1. Mwanaume
|
||||
2. Mwanamke
|
||||
3. Nyngine
|
||||
0. Rudi","CON Ikia gender yaku
|
||||
1. Mundume
|
||||
2. Mundumuka
|
||||
3. Ingi
|
||||
0. Syoka itina","CON We mudurume kana mutumia
|
||||
1.Mudurume
|
||||
2. Mutumia
|
||||
3. Ingi
|
||||
0. Coka thutha","CON Ika kala Umulume ama Umuche au vingine.
|
||||
1. Mulume
|
||||
2. Muche
|
||||
3. Vinjine
|
||||
0. Uya Nyuma","CON Ket kit chwech mari
|
||||
1. Dichuo
|
||||
2. Dhako
|
||||
3. Moko
|
||||
0. Dog chien","CON Athin Dir mo Dubr
|
||||
1. Dir
|
||||
2. Dubr
|
||||
3. Ka dibii
|
||||
0. Dheebi"
|
||||
enter_location,"CON Enter your location,
|
||||
0. Back","CON Weka eneo lako
|
||||
0. Rudi","CON Ikia utui waku kana location
|
||||
0. Syoka itina","CON Ekera kuria uumete
|
||||
0. Coka thutha","CON Ika enero wombolako.
|
||||
0. Uya nyuma","CON Ket kumaidake
|
||||
0. Dog chien","CON Fulaa athin kubat kor
|
||||
0. Dhebii"
|
||||
enter_products,"CON Please enter a product or service you offer
|
||||
0. Back","CON Weka bidhaa ama huduma unauza
|
||||
0. Rudi","CON Ikia syindu kana huduma ila unenganae
|
||||
0. Syoka itina","CON Ekera indo kana wira uria urendia
|
||||
0. Coka thutha","CON Ika Viya ama utu uhendao
|
||||
0. Uya Nyuma","CON Ket gima iuso kata tich mitimo
|
||||
0. Dog chien","CON Waan gurgurt okan namaa kenit khes khae
|
||||
0. Dheebi"
|
||||
start,"CON Balance %{account_balance} %{account_token_name}
|
||||
1. Send
|
||||
2. My Sarafu
|
||||
3. My Account
|
||||
4. Help","CON Salio %{account_balance} %{account_token_name}
|
||||
1. Tuma
|
||||
2. Sarafu yangu
|
||||
3. Akaunti yangu
|
||||
4. Usaidizi","CON Mbalansi kana utyalyo %{account_balance} %{account_token_name}
|
||||
1. Tuma
|
||||
2. Kinandu chakwa
|
||||
3. Utethyo","CON Matigari %{account_balance} %{account_token_name}
|
||||
1. Tuma
|
||||
2. Akaunti yaku
|
||||
3. Uteithio","CON Sazo %{account_balance} %{account_token_name}
|
||||
1. Huma
|
||||
2. Akaunti yangu
|
||||
3. Avizwa","CON Ma Odong' %{account_balance} %{account_token_name}
|
||||
1. Or
|
||||
2. Akaont na
|
||||
3. Kony","CON Salio %{account_balance} %{account_token_name}
|
||||
1. Erg
|
||||
2. Akaunti khiy
|
||||
3. Qarqars"
|
||||
enter_transaction_recipient,"CON Enter phone number
|
||||
0. Back","CON Weka nambari ya simu
|
||||
0. Rudi","CON Ikia namba ya simu
|
||||
0. Syoka itina","CON Ikira namba ya thimu
|
||||
0. Coka thutha","CON Ika namba yasimu.
|
||||
0. Uya Nyuma","CON Ket nambani mar simu
|
||||
0. Dog chien","CON Namba ta simuu kekhai
|
||||
0. Dheebi"
|
||||
enter_transaction_amount,"CON Enter amount
|
||||
0. Back","CON Weka kiwango
|
||||
0. Rudi","CON Ikia kiwango
|
||||
0. Syoka itina","CON Ikira muigana
|
||||
0. Coka thutha","CON Ika chaasi.
|
||||
0. Uya nyuma","CON Ket giko mari
|
||||
0. Dog chien","CON kiwango kekhai
|
||||
0. Dheebi"
|
||||
first_account_tokens_set,"CON Choose a number or symbol from your balances:
|
||||
%{first_account_tokens_set}
|
||||
|
||||
0. Back
|
||||
11. Next
|
||||
00. Exit","CON Chagua nambari au ishara kutoka kwa salio zako:
|
||||
%{first_account_tokens_set}
|
||||
|
||||
0. Rudi
|
||||
11. Mbele
|
||||
00. Ondoka","Sakua Sarafu:
|
||||
%{token_list}
|
||||
99. Thi mbee
|
||||
00. Syoka itina","Shaghura Sarafu:
|
||||
%{token_list}
|
||||
99. Mbere
|
||||
00. Coka thutha","Tsagula Sarafu:
|
||||
%{token_list}
|
||||
99. Enderera
|
||||
00. Uya Nyuma","Yier Sarafu:
|
||||
%{token_list}
|
||||
99. Nyime
|
||||
00. Dog chien","Chaqui Sarafu:
|
||||
%{token_list}
|
||||
99. Dhuur
|
||||
00. Dheebi"
|
||||
middle_account_tokens_set,"CON Choose a number or symbol from your balances:
|
||||
%{middle_account_tokens_set}
|
||||
11. Next
|
||||
22. Previous
|
||||
00. Exit","CON Chagua nambari au ishara kutoka kwa salio zako:
|
||||
%{middle_account_tokens_set}
|
||||
11. Mbele
|
||||
22. Rudi
|
||||
00. Ondoka","Sakura Sarafu:
|
||||
%{token_list}
|
||||
99. Thi mbee
|
||||
00. Syoka itina","Shaghura Sarafu:
|
||||
%{token_list}
|
||||
99. Mbere
|
||||
00. Cooka thutha","Tsagula Sarafu:
|
||||
%{token_list}
|
||||
99. Enderera
|
||||
00. Uya Nyuma","Yier Sarafu:
|
||||
%{token_list}
|
||||
99. Nyime
|
||||
00. Dog chien","Chaqui Sarafu:
|
||||
%{token_list}
|
||||
99. Dhuur
|
||||
00. Dheebi"
|
||||
last_account_tokens_set,"CON Choose a number or symbol from your balances:
|
||||
%{last_account_tokens_set}
|
||||
22. Previous
|
||||
00. Exit","CON Chagua nambari au ishara kutoka kwa salio zako:
|
||||
%{last_account_tokens_set}
|
||||
22. Rudi
|
||||
00. Ondoka","Sakura sarafu:
|
||||
%{token_list}
|
||||
00. Syoka itina","Sarafu:
|
||||
%{token_list}
|
||||
00. Cooka thutha","Tsagula Sarafu:
|
||||
%{token_list}
|
||||
00. Uya Nyuma","Yier Sarafu:
|
||||
%{token_list}
|
||||
00. Dog chien","Chagua Sarafu:
|
||||
%{token_list}
|
||||
00. Dheebi"
|
||||
token_selection_pin_authorization.first,"CON %{token_data}
|
||||
Enter pin to select:","CON %{token_data}
|
||||
Weka nambari ya siri kuchagua:
|
||||
0. Back","%{token_info}
|
||||
Sakua kwa kwikia pin yaku:
|
||||
0. Syoka itina","%{token_info}
|
||||
Ekera pin yaku gushaghura:
|
||||
0. Cooka thutha","%{token_info}
|
||||
Ika piniyo kutsagula Sarafu:
|
||||
0. Uya Nyuma","%{token_info}
|
||||
Ket pin ni iyier:
|
||||
0. Dog chien","%{token_info}
|
||||
Pin kake khai akh dibii chaguat
|
||||
0. Dheebi"
|
||||
token_selection_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry}
|
||||
account_management,"CON My account
|
||||
1. My profile
|
||||
2. Change language
|
||||
3. Check balance
|
||||
4. Check statement
|
||||
5. PIN options
|
||||
0. Back","CON Akaunti yangu
|
||||
1. Wasifu wangu
|
||||
2. Chagua lugha utakayotumia
|
||||
3. Angalia salio
|
||||
4. Angalia taarifa ya matumizi
|
||||
5. Mipangilio ya nambari ya siri
|
||||
0. Rudi","CON Kinandu Chakwa
|
||||
1. Profile/wasifu wakwa
|
||||
2. Sakua luka ila ukatumiaa
|
||||
3. Syisya Mbalansi yaku
|
||||
4. Syisya welesyo wa utumii
|
||||
5. Chenja namba yaku ya siri
|
||||
0. Syoka itina","CON Akaunti yakwa
|
||||
1. Maritwa makwa
|
||||
2. Shaghura rothiumu ukuhuthira
|
||||
3. Rora matigari
|
||||
4. Rora rugano rwa mahuthira
|
||||
5. Chengia namba ya thiri
|
||||
0. Coka","CON Akaunti yangu
|
||||
1. Malagizo Gangu
|
||||
2. Tsagula Luga Undohumira
|
||||
3. Lola Sazo
|
||||
4. Lola tarifa Za Mahumizi
|
||||
5. Galuza Namba Fitse
|
||||
0. Uya Nyuma","CON Akaont na
|
||||
1. Nyanonro mara
|
||||
2. Yier dhok midwatiyogo
|
||||
3. Ngi midong go
|
||||
4. N'gi chal mar akaont
|
||||
5. Lok nambani mopondo
|
||||
0. Dog chien","CON Akaunti khiy
|
||||
1. Wasifu wangu
|
||||
2. Afaan dubaad chaqui
|
||||
3. laali balansi
|
||||
4. Angalia taarifa ya matumizi
|
||||
5. Gargarch namba
|
||||
0. Dheebi"
|
||||
metadata_management,"CON My profile
|
||||
1. Edit name
|
||||
2. Edit gender
|
||||
3. Edit age
|
||||
4. Edit location
|
||||
5. Edit products
|
||||
6. View my profile
|
||||
0. Back","CON Wasifu wangu
|
||||
1. Weka jina
|
||||
2. Weka jinsia
|
||||
3. Weka umri
|
||||
4. Weka eneo
|
||||
5. Weka bidhaa
|
||||
6. Angalia wasifu wako
|
||||
0. Rudi","CON Profile/Wasifu wakwa
|
||||
1. Ikia isyitwa
|
||||
2. Ikia jinsia/gender yaku
|
||||
3. Ikia miaka yaku
|
||||
4. Ikia utui waku
|
||||
5. Ikia syindu ila utesaa
|
||||
6. Sisya profile/wasifu waku
|
||||
0. Syoka itina","CON Maondu maku
|
||||
1. Ekera ritwa
|
||||
2. Ekera kana we mundurume kana mutumia
|
||||
3. Ekera miaka yaku
|
||||
4. Ekera kuria uikaraga
|
||||
5. Ikira kiria uendagia
|
||||
6. Rora maundu maku
|
||||
0. Coka thutha","CON Malagizo Gangu
|
||||
1. Ika dzinaro
|
||||
2. Ika kala umulume ama Umuche
|
||||
3. Ika umuri
|
||||
4. Ika eneo
|
||||
5. Ika Miyo ama viya uguzavyo
|
||||
6. Lola malagizo Gangu
|
||||
0. Uya nyuma","CON Wasifu wangu
|
||||
1. Ket nyingi
|
||||
2. Ket kit chuech mari
|
||||
3. Ket iki
|
||||
4. Ket kumaidake
|
||||
5. Ket gikmaiuso
|
||||
6. Ng'i nyanonro mara
|
||||
0. Dog chien","CON Wasifu wangu
|
||||
1. Maqa kekhai
|
||||
2. Naam dira mo dubr
|
||||
3. Gan kekhai
|
||||
4. Fulaa itgalt kai
|
||||
5. Mih kai
|
||||
6. Angalia wasifu wako
|
||||
0. Dheebi"
|
||||
display_user_metadata,"CON Your details are:
|
||||
Name: %{full_name}
|
||||
Gender: %{gender}
|
||||
Age: %{age}
|
||||
Location: %{location}
|
||||
You sell: %{products}
|
||||
0. Back","CON Wasifu wako una maelezo yafuatayo:
|
||||
Jina: %{full_name}
|
||||
Jinsia: %{gender}
|
||||
Umri: %{age}
|
||||
Eneo: %{location}
|
||||
Unauza: %{products}
|
||||
0. Rudi","CON Profile/Wasifu waku wina maelesyo aa:
|
||||
Isyitwa: %{full_name}
|
||||
Jinsia yaku/gender: %{gender}
|
||||
Miaka yaku: %{age}
|
||||
Utui/location yaku: %{location}
|
||||
Syindu ila uta: %{products}
|
||||
0. Syoka itina","CON Maundu maku mena rugano ruru:
|
||||
Maretwa: %{full_name}
|
||||
Mutumia kana muthuri: %{gender}
|
||||
Miaka : %{age}
|
||||
Kuria uikaraga : %{location}
|
||||
Kiria uendagia : %{products}
|
||||
0. Coka thutha","CON Malagizo gako gana moro uthuwizirao:
|
||||
Dzina: %{full_name}
|
||||
Umuche ama Mulume: %{gender}
|
||||
Umuri: %{age}
|
||||
Umbolako: %{location}
|
||||
Miyo uguzayo: %{products}
|
||||
0. Uya nyuma","CON Nyanonro mari en:
|
||||
Nying: %{full_name}
|
||||
Kit chuech: %{gender}
|
||||
Iga: %{age}
|
||||
Kumidake: %{location}
|
||||
Gima iuso: %{products}
|
||||
0. Dog chien","CON Wasifu wako una maelezo yafuatayo:
|
||||
JinaMakha: %{full_name}
|
||||
Jinsia: %{gender}
|
||||
Gan: %{age}
|
||||
Fulaa : %{location}
|
||||
Maan gurgurt: %{products}
|
||||
0. Dheebi"
|
||||
select_preferred_language,"CON Choose language:
|
||||
%{first_language_set}
|
||||
|
||||
0. Back
|
||||
11. Next
|
||||
00. Exit","CON Chagua lugha:
|
||||
%{first_language_set}
|
||||
|
||||
0. Rudi
|
||||
11. Mbele
|
||||
00. Ondoka","CON Sakua luka
|
||||
1. Kisungu
|
||||
2. Kiswahili
|
||||
3. Kikamba
|
||||
0. Syoka itina","CON Caghura ruthiomi
|
||||
1. Githungu
|
||||
2. Githweri
|
||||
0. Coka","CON Tsagula Luga
|
||||
1. Kizungu
|
||||
2. Kiswahili
|
||||
0. Uya nyuma","CON Yier dhok
|
||||
1. Dho Ngere
|
||||
2. Dho Oswayo
|
||||
0. Dog chien","CON Chagua lugha
|
||||
1. Afaan ferenji
|
||||
2. Afaan kiswahili
|
||||
0. Dheebi"
|
||||
retry_pin_entry,"CON Incorrect PIN entered,please try again. You have %{remaining_attempts} attempts remaining.
|
||||
0. Back","CON Nambari uliyoweka si sahihi, jaribu tena. Una majaribio %{remaining_attempts} yaliyobaki.
|
||||
0. Rudi","CON Namba ila wekiya iyaile, tata kwikia ingi. Tata mala %{remaining_attempts} nimo matyele.
|
||||
0. Itina","CON Namba uikirite ti njega, geria ringi.Ni maita %{remaining_attempts} matigarete.
|
||||
0. Gucoka thutha","CON Nambari fitse urioika seyo, jeza kaheri. Usere Majezo %{remaining_attempts} Gaserego.
|
||||
0. Uya nyuma","CON Namba miketo oknikare, tem kendo. Idong gi temo di %{remaining_attempts} modong.
|
||||
0. Chien","CON Namba at keket suninit,laal amalle.Nafaas kaitdheebit %tanaataf
|
||||
0. Dheebi"
|
||||
pin_management,"CON Pin options
|
||||
1. Change PIN
|
||||
2. Reset PIN
|
||||
3. Guard PIN
|
||||
0. Back","CON Pin options
|
||||
1. Badilisha nambari yangu ya siri
|
||||
2. Tuma ombili la kubadilisha nambari ya siri
|
||||
3. Linda nambari ya siri
|
||||
0. Rudi",,,,,
|
||||
enter_current_pin.first,"CON Enter current PIN.
|
||||
0. Back","CON Weka nambari ya siri.
|
||||
0. Rudi","CON Ikia namba yaku ya siri.
|
||||
0. Syoka itina","CON Ekera namba ya thiri
|
||||
0. Coka thutha","CON Ika namba fitse.
|
||||
0. Uya Nyuma","CON Ket nambani mopondo.
|
||||
0. Dog chien","CON Namba ka namii imben kekhai
|
||||
0. Dheebi"
|
||||
enter_current_pin.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry}
|
||||
enter_new_pin,"CON Enter your new four number PIN
|
||||
0. Back","CON Weka nambari ya siri mpya
|
||||
0. Rudi","CON Ikia namba yaku ya siri ila yumbya
|
||||
0. Syoka itina","CON Ekera namba njeru ya thiri
|
||||
0. coka thutha","CON Ika namba fitse mbisha
|
||||
0. Uya nyuma","CON Ket namba mopondo maanyien.
|
||||
0. Dog chien","CON Namba hareti ka namii imben kekhai
|
||||
0. Dheebi"
|
||||
new_pin_confirmation,"CON Enter your new four number PIN again
|
||||
0. Back","CON Weka nambari yako ya siri tena
|
||||
0. Rudi","CON Ikia namba yaku ya siri ingi
|
||||
0. Syoka itina","CON Ekera namba yaku ya thiri renge
|
||||
0. Coka thutha","CON Uyira kuika lwaphiri
|
||||
0. Uya Nyuma","CON Ket nambani mopondo kendo
|
||||
0. Dog chien","CON Namba hareti ka namii imben kekhai amalle
|
||||
0. Dheebi"
|
||||
reset_guarded_pin,"CON Enter phone number you are the guardian to reset their pin
|
||||
0. Back","CON Weka nambari ya simu ili kutuma ombi la kubalisha nambari ya siri.
|
||||
0. Rudi",,,,,
|
||||
reset_guarded_pin_authorization.first,"CON Enter YOUR pin to confirm %{guarded_account_information}'s reset
|
||||
0. Back","CON Weka nambari YAKO ya siri ili kudhibitisha ombi la kubadilisha nambari ya siri ya %{guarded_account_information}.
|
||||
0. Rudi",,,,,
|
||||
reset_guarded_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},,,,,
|
||||
exit_pin_reset_initiated_success,"CON Success: You have initiated a PIN reset for %{guarded_account_information}
|
||||
0. Back
|
||||
9. Exit","CON Ombi lako la kubadili nambari ya siri ya %{guarded_account_information} limetumwa.
|
||||
0. Rudi
|
||||
9. Ondoka",,,,,
|
||||
exit_not_authorized_for_pin_reset,"CON Failure: You are not authorized to reset that PIN. You must be a guardian!
|
||||
0. Back
|
||||
9. Exit","CON Huruhusiwi kutuma ombi la kubadilisha nambari ya siri.
|
||||
0. Rudi
|
||||
9. Ondoka",,,,,
|
||||
guard_pin,"CON Pin guard
|
||||
1. View guardians
|
||||
2. Add guardian
|
||||
3. Remove guardian
|
||||
0. Back","CON Linda nambari ya siri
|
||||
1. Walinzi wa namabari ya siri
|
||||
2. Ongeza mlinzi
|
||||
3. Ondoa mlinzi
|
||||
0. Rudi",,,,,
|
||||
guardian_list_pin_authorization.first,"CON Enter your pin to view set guardians
|
||||
0. Back","CON Weka nambari yako ya siri ili kuona walinzi uliowaongeza
|
||||
0. Rudi",,,,,
|
||||
guardian_list_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},,,,,
|
||||
guardian_list,"CON %{guardians_list}
|
||||
0. Back
|
||||
9. Exit","CON %{guardians_list}
|
||||
0. Rudi
|
||||
9. Ondoka",,,,,
|
||||
add_guardian,"CON Enter phone number to add as pin reset guardian
|
||||
0. Back","CON Weka nambari ya simu ili kuongeza mlinzi
|
||||
0. Rudi",,,,,
|
||||
add_guardian_pin_authorization.first,"CON Enter your pin to add %{guardian_information} as your PIN reset guardian
|
||||
0. Back","CON Weka nambari YAKO ya siri ili kumwongeza %{guardian_information} kama mlinzi
|
||||
0. Rudi",,,,,
|
||||
add_guardian_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},,,,,
|
||||
exit_guardian_addition_success,"CON Success: %{guardian_information} can now reset your PIN
|
||||
0. Back
|
||||
9. Exit","CON Ombi lako la kumwongeza: %{guardian_information} kama mlinzi limefanikiwa
|
||||
0. Rudi
|
||||
9. Ondoka",,,,,
|
||||
exit_invalid_guardian_addition,"CON %{error_exit}
|
||||
0. Back
|
||||
9. Exit","CON %{error_exit}
|
||||
0. Rudi
|
||||
9. Ondoka",,,,,
|
||||
remove_guardian,"CON Enter phone number to revoke guardianship:
|
||||
0. Back","CON Weka nambari ya simu ili kuondoa mlinzi
|
||||
0. Rudi",,,,,
|
||||
remove_guardian_pin_authorization.first,"CON Enter your pin to remove %{guardian_information} as your PIN reset guardian
|
||||
0. Back","CON Weka nambari YAKO ya siri ili kumwondoa %{guardian_information} kama mlinzi
|
||||
0. Rudi",,,,,
|
||||
remove_guardian_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},,,,,
|
||||
exit_guardian_removal_success,"CON Success: %{guardian_information} PIN reset guardianship is revoked
|
||||
0. Back
|
||||
9. Exit","CON Ombi lako la kumwondoa: %{guardian_information} kama mlinzi limefanikiwa
|
||||
0. Rudi
|
||||
9. Ondoka",,,,,
|
||||
exit_invalid_guardian_removal,"CON %{error_exit}
|
||||
0. Back
|
||||
9. Exit","CON %{error_exit}
|
||||
0. Rudi
|
||||
9. Ondoka",,,,,
|
||||
transaction_pin_authorization.first,"CON %{recipient_information} will receive %{transaction_amount} %{token_symbol} from %{sender_information}.
|
||||
Please enter your PIN to confirm.
|
||||
0. Back","CON %{recipient_information} atapokea %{transaction_amount} %{token_symbol} kutoka kwa %{sender_information}.
|
||||
Tafadhali weka nambari yako ya siri kudhibitisha.
|
||||
0. Rudi","CON %{recipient_information} nukwata %{transaction_amount} %{token_symbol} kuma kwa %{sender_information}.
|
||||
Tafadhali ikia namba yaku ya siri kuvitukithya.
|
||||
0. Syoka itina","CON %{recipient_information} akuamukira %{transaction_amount} %{token_symbol} kuuma kwa %{sender_information}.
|
||||
Ekera namba yaku ya thiri kuetekeria.
|
||||
0. Coka thutha","CON %{recipient_information} atapokea %{transaction_amount} %{token_symbol} kutoka kwa %{sender_information}.
|
||||
Unavoywa kuika nambayo fitse kugeluza.
|
||||
0. Uya nyuma.","CON %{recipient_information} dhiyudo %{transaction_amount} %{token_symbol} kowuok kuom %{sender_information}.
|
||||
Kiyie to ket nambani mopondo mondo iyie:
|
||||
0. Dog chien","CON %{recipient_information} in argad%{transaction_amount} %{token_symbol} ir %{sender_information}.
|
||||
Namba ka namii imben kekhai
|
||||
0. Dheebi"
|
||||
transaction_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry}
|
||||
display_metadata_pin_authorization.first,"CON Please enter your PIN
|
||||
0. Back","CON Tafadhali weka PIN yako
|
||||
0. Rudi","CON Tafadhali ikia PIN yaku
|
||||
0. Syoka itina","CON Ekera pin yaku
|
||||
0. coka thutha","CON Unavoywa kuika namayo fitswe
|
||||
0. Uya Nyuma","CON Kiyie to ket nambani mopondo
|
||||
0. Dog chien","CON Namba ka namii imben kekhai
|
||||
0. Dheebi"
|
||||
display_metadata_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry}
|
||||
account_balances_pin_authorization.first,"CON Please enter your PIN to view balances
|
||||
0. Back","CON Tafadhali weka PIN yako kuona salio.
|
||||
0. Rudi","CON Tafadhali ikia PIN yaku kwona utyalo.
|
||||
0. Syoka itina","CON Ekera pin yaku kuona matigari maku
|
||||
0. Coka ","CON Unavoywa namba fitswe kulola Sazo.
|
||||
0. Uya nyuma","CON Kiyie to ket nambani mopondo mondo ine modong'
|
||||
0. Dog chien","CON Namba ka namii imbeen kekhai ak balansi kake lalt
|
||||
0. Dheebi"
|
||||
account_balances_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry}
|
||||
account_statement_pin_authorization.first,"CON Please enter your PIN to view statement
|
||||
0. Back","CON Tafadhali weka PIN yako kuona taarifa ya matumizi.
|
||||
0. Rudi","CON Tafadhali ikia PIN yaku kwona welesyo wa utumii.
|
||||
0. Syoka itina","CON Ekera pin yaku kuona rugano rwa mahuthira maku
|
||||
0. coka thutha","CON Unavoywa namba fitswe kupata maerezo ga mahumizi Gako.
|
||||
0. Uya Nyuma","CON Kiyie to ket nambani mar siri mondo ine chenro mar tiyo.
|
||||
0. Dog chien","CON Tafadhali weka PIN yako kuona taarifa ya matumizi.
|
||||
0. Dheebi"
|
||||
account_statement_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry}
|
||||
name_edit_pin_authorization.first,"CON Please enter your PIN
|
||||
0. Back","CON Tafadhali weka PIN yako
|
||||
0. Rudi","CON Tafadhali ikia PIN yaku
|
||||
0. Syoka itina","CON Ekera pin yaku
|
||||
0. coka thutha","CON Unavoywa namba fitse
|
||||
0. Uya Nyuma","CON Ket nambani mopondo
|
||||
0. Dog chien","CON Namba ka namii imben kekhai
|
||||
0. Dheebi"
|
||||
name_edit_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry}
|
||||
dob_edit_pin_authorization.first,"CON Please enter your PIN
|
||||
0. Back","CON Tafadhali weka PIN yako
|
||||
0. Rudi","CON Tafadhali ikia PIN yaku
|
||||
0. Syoka itina","CON Ekera namba yaku ya thiri
|
||||
0. Rudi","CON Unavoywa namba fitswe
|
||||
0. Uya nyuma","CON Kiyie to ket nambani mopondo
|
||||
0. Dog chien","CON Namba kake ka namii imbeen kekhai
|
||||
0. Dheebi"
|
||||
dob_edit_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry}
|
||||
gender_edit_pin_authorization.first,"CON Please enter your PIN
|
||||
0. Back","CON Tafadhali weka PIN yako
|
||||
0. Rudi","CON Tafadhali ikia PIN yaku
|
||||
0. Syoka itina","CON Ekera namba yaku ya thiri
|
||||
0. coka thutha","CON Unavoywa namba fitswe
|
||||
0. Uya nyuma","CON Kiyie to ket nambani mopondo
|
||||
0. Dog chien","CON Namba kake ka namii imbeen kekhai
|
||||
0. Dheebi"
|
||||
gender_edit_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry}
|
||||
location_edit_pin_authorization.first,"CON Please enter your PIN
|
||||
0. Back","CON Tafadhali weka PIN yako
|
||||
0. Rudi","CON Tafadhali ikia PIN yaku
|
||||
0. Syoka itina","CON Ekera namba yaku ya thiri
|
||||
0. Coka thutha","CON Unavoywa namba fitswe
|
||||
0. Uya nyuma","CON Kiyie to ket nambani mopondo
|
||||
0. Dog chien","CON Namba kake ka namii imbeen kekhai
|
||||
0. Dheebi"
|
||||
location_edit_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry}
|
||||
products_edit_pin_authorization.first,"CON Please enter your PIN
|
||||
0. Back","CON Tafadhali weka PIN yako
|
||||
0. Rudi","CON Tafadhali ikia PIN yaku
|
||||
0. Syoka itina","CON Ekera namba yaku ya thiri
|
||||
0. Coka thutha","CON Unavoywa namba fitswe
|
||||
0. Uya nyuma","CON Kiyie to ket nambani mopondo
|
||||
0. Dog chien","CON Namba kake ka namii imbeen kekhai
|
||||
0. Dheebi"
|
||||
products_edit_pin_authorization.retry,%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry},%{retry_pin_entry}
|
||||
account_balances.available_balance,"CON Your balances are as follows:
|
||||
balance: %{available_balance} %{token_symbol}
|
||||
0. Back","CON Salio zako ni zifuatazo:
|
||||
salio: %{available_balance} %{token_symbol}
|
||||
0. Rudi","CON Utyalo waku ni uu:
|
||||
utyalo: %{available_balance} %{token_symbol}
|
||||
0. Syoka itina","CON Matigari maku ni maya:
|
||||
matigari: %{available_balance} %{token_symbol}
|
||||
0. coka","CON Masazogo nidzavivyo gatuwiravyo:
|
||||
Sazo: %{available_balance} %{token_symbol}
|
||||
0. Uya Nyuma","CON Dong mari en:
|
||||
Dong: %{available_balance} %{token_symbol}
|
||||
0. Dog chien","CON Balansi kake akan
|
||||
salio: %{available_balance} %{token_symbol}
|
||||
0. Dheebi"
|
||||
account_balances.with_fees,"CON Your balances are as follows:
|
||||
balances: %{available_balance} %{token_symbol}
|
||||
fees: %{tax} %{token_symbol}
|
||||
0. Back","CON Salio zako ni zifuatazo:
|
||||
salio: %{available_balance} %{token_symbol}
|
||||
ushuru: %{tax} %{token_symbol}
|
||||
0. Rudi","CON Utyalo waku ni uu:
|
||||
utyalo: %{available_balance} %{token_symbol}
|
||||
tax/ushuru: %{tax} %{token_symbol}
|
||||
0. Syoka itina","CON Matigari maku ni maya:
|
||||
matigari: %{available_balance} %{token_symbol}
|
||||
ushuru: %{tax} %{token_symbol}
|
||||
0. coka thutha","CON Masazogo ni gatuwirago:
|
||||
Masazo: %{available_balance} %{token_symbol}
|
||||
Ushuuru: %{tax} %{token_symbol}
|
||||
0. Uya nyuma","CON Dong mari en:
|
||||
Dong: %{available_balance} %{token_symbol}
|
||||
osuru: %{tax} %{token_symbol}
|
||||
0. Dog chien","CON Balansi kake akan
|
||||
salio: %{available_balance} %{token_symbol}
|
||||
ushuru: %{tax} %{token_symbol}
|
||||
0. Dheebi"
|
||||
account_balances.with_rewards,"CON Your balances are as follows:
|
||||
balance: %{available_balance} %{token_symbol}
|
||||
fees: %{tax} %{token_symbol}
|
||||
rewards: %{bonus} %{token_symbol}
|
||||
0. Back","CON Salio zako ni zifuatazo:
|
||||
salio: %{available_balance} %{token_symbol}
|
||||
ushuru: %{tax} %{token_symbol}
|
||||
tuzo: %{bonus} %{token_symbol}
|
||||
0. Rudi","CON Utyalo waku ni uu:
|
||||
Utyalo: %{available_balance} %{token_symbol}
|
||||
Tax/ushuru: %{tax} %{token_symbol}
|
||||
muthinzio: %{bonus} %{token_symbol}
|
||||
0. Syoka itina","CON Salio zako ni zifuatazo:
|
||||
salio: %{available_balance} %{token_symbol}
|
||||
ushuru: %{tax} %{token_symbol}
|
||||
tuzo: %{bonus} %{token_symbol}
|
||||
0. coka thutha","CON Sazoro ni rituwiraro:
|
||||
Sazo: %{available_balance} %{token_symbol}
|
||||
Ushuuru: %{tax} %{token_symbol}
|
||||
Zawadi: %{bonus} %{token_symbol}
|
||||
0. Uya Nyuma","CON Dong mari en:
|
||||
Dong: %{available_balance} %{token_symbol}
|
||||
osuru: %{tax} %{token_symbol}
|
||||
mich: %{bonus} %{token_symbol}
|
||||
0. Dog chien","CON Balansi kake akan
|
||||
salio: %{available_balance} %{token_symbol}
|
||||
ushuru: %{tax} %{token_symbol}
|
||||
tuzo: %{bonus} %{token_symbol}
|
||||
0. Dheebi"
|
||||
first_transaction_set,"CON %{first_transaction_set}
|
||||
|
||||
0. Back
|
||||
11. Next
|
||||
00. Exit","CON %{first_transaction_set}
|
||||
|
||||
0. Rudi
|
||||
11. Mbele
|
||||
00. Ondoka","CON %{first_transaction_set}
|
||||
1. Mbee
|
||||
00. Ondoka","CON %{first_transaction_set}
|
||||
1. Mbere
|
||||
00. uma","CON %{first_transaction_set}
|
||||
1. Mbere
|
||||
00. Uka","CON %{first_transaction_set}
|
||||
1. Nyime
|
||||
00. Wuogi","CON %{first_transaction_set}
|
||||
1. Dhuur
|
||||
00. Bai"
|
||||
middle_transaction_set,"CON %{middle_transaction_set}
|
||||
11. Next
|
||||
22. Previous
|
||||
00. Exit","CON %{middle_transaction_set}
|
||||
11. Mbele
|
||||
22. Rudi
|
||||
00. Ondoka","CON %{middle_transaction_set}
|
||||
1. Mbee
|
||||
2. Itina
|
||||
00. Ondoka","CON %{middle_transaction_set}
|
||||
1. Mbere
|
||||
2. coka thutha
|
||||
00. Uma","CON %{middle_transaction_set}
|
||||
1. Mbere
|
||||
2. Uya nyuma
|
||||
00. Uka","CON %{middle_transaction_set}
|
||||
1. Nyime
|
||||
2. Dog chien
|
||||
00. Wuogi","CON %{middle_transaction_set}
|
||||
1. Dhuur
|
||||
2. Dheebi
|
||||
00. Bai"
|
||||
last_transaction_set,"CON %{last_transaction_set}
|
||||
22. Previous
|
||||
00. Exit","CON %{last_transaction_set}
|
||||
22. Rudi
|
||||
00. Ondoka","CON %{last_transaction_set}
|
||||
2. Itina
|
||||
00. Ondoka","CON %{last_transaction_set}
|
||||
2. Coka thutha
|
||||
00. Uma","CON %{last_transaction_set}
|
||||
2. Uya Nyuma
|
||||
00. Uka","CON %{last_transaction_set}
|
||||
2. Dog chien
|
||||
00. Wuogi","CON %{last_transaction_set}
|
||||
2. Dhuur
|
||||
00. Bai"
|
||||
exit,END Thank you for using the service.,END Asante kwa kutumia huduma.,END Ni muvea kwa kutumia huduma ii.,END Thegio ni kuhuthira mutabo,END. Namvera kwa mahumizi ga ii huduma.,END Erokamano kuom tiyo kodwa.,END Asante kwa kutumia huduma.
|
||||
exit_invalid_request,END Invalid request.,END Chaguo si sahihi.,END Usakuo waku uyaile.,END shaguro riaku ti riega,END. Tsagulo karisawa sawa,END Yiero okni kare,END Ka at chaquad suninit
|
||||
exit_invalid_menu_option,"CON Invalid menu option. For help,call %{support_phone}.
|
||||
00. Back
|
||||
99. Exit","CON Chaguo lako sio sahihi. Kwa usaidizi piga simu %{support_phone}
|
||||
00. Rudi
|
||||
99. Ondoka","CON Usakuo waku uyaile. Kwa utethyo kuna simu %{support_phone}
|
||||
00. Itina
|
||||
99. Ondoka","CON Shaguro riaku ti riega.Kwa uteithio hura %{support_phone}
|
||||
00. Coka thutha
|
||||
99. Uma","CON Tsaguloro karisawa sawa. Kuavizwa piga %{support_phone}
|
||||
00. Uya Nyuma
|
||||
99. Uka","CON Yiero ni oknikare. Kuom kony go simu e %{support_phone}
|
||||
00. Dog chien
|
||||
99. Wuogi","CON Ka at chaqui suninit.qarqarsa simu dai%{support_phone}
|
||||
00. Dheebi
|
||||
99. Bai"
|
||||
exit_invalid_input,"CON Invalid input. Nothing selected
|
||||
00. Back
|
||||
99. Exit","CON Chaguo lako halipatikani. Hakuna kilichochaguliwa.
|
||||
00. Rudi
|
||||
99. Ondoka","CON Usakuo waku wikwonekana.Vaii kindu kisakue.
|
||||
00. Itina
|
||||
99. Ondoka","CON Shaguro riaku ritironekana. Hatiri kindu washaghura.
|
||||
00. Coka thutha
|
||||
99. Uma","CON Tsaguloro karipatikana. Kakuna Kutsagurire chochosi.
|
||||
00. Uya nyuma
|
||||
99. Uka","CON Yiero ni okyudre. Onge gima iyiero.
|
||||
00. Dog chien
|
||||
99. Wuogi","CON Ka at chaguad injirt. oo
|
||||
00. dheebi
|
||||
99. Bai"
|
||||
exit_pin_blocked,"END Your PIN has been blocked. For help, please call %{support_phone}.",END PIN yako imefungwa. Kwa usaidizi tafadhali piga simu %{support_phone}.,END PIN yaku niyavingwa. Kutethwa kuna simu ino %{support_phone}.,END PIN yaku niyahingwo. Kwa uteithio hura thimu %{support_phone}.,END. Namba fitse yakwako ifungwa. Kwa kuavizwa unaangwa upige simu %{support_phone}.,END Nambani mopondo olor. Kuom kony go simu e %{support_phone}.,END Pin kake yahidat. Qarqarsa simu dai %{support_phone}.
|
||||
exit_invalid_pin,"END The PIN you have entered is invalid. PIN must consist of 4 digits. For help, call %{support_phone}.",END PIN uliyobonyeza sio sahihi. PIN lazima iwe na nambari nne. Kwa usaidizi piga simu %{support_phone}.,END PIN ila wekia iyaile. Ni lasima PIN ithiwe na namba inya. Kutethwa kuna namba ii %{support_phone}.,END PIN iria wekera tii njega. PIN nomoka ikorwo na namba inya. Kwa uteithio hura thimu %{support_phone}.,"END Namba fitse urohofya seyo, kaisawa. Namba fitswe inamalwa ikale na namba nee. Kwa kuvizwa, piga simu%{support_phone}.",END. Namba mopondo miketo oknikare. Nyaka obed gi nembni ang'wen. Kuom kony go simu e %{support_phone}.,END PIN ka at keket suninit. PIN Pin namba afuuri tatatu. Qarqarsa simu dai %{support_phone}.
|
||||
exit_invalid_new_pin,"END The PIN you have entered is invalid. PIN must be different from your current PIN. For help, call %{support_phone}.",END PIN uliyobonyeza sio sahihi. PIN lazima iwe tofauti na pin yako ya sasa. Kwa usaidizi piga simu %{support_phone}.,END PIN ila wekia iyaile. PIN ni lasima ithiwe tofauti na pin yaku ya oyu. Kutethwa kuna namba ii %{support_phone}.,END PIN uria wekera ti njega. PIN nomohaka ikorwo na namba ndiganu na ya riu . Kwa uteithio hora thimu %{support_phone}.,END Namba fitswe uriohopya siyo ya karakara. Namba fitswe inahenzekana ikale itofauti na uhumirayo vivi. Kwa maavizo piga simu %{support_phone}.,END Namba mopondo miketo oknikare. Nyaka obed mopogore gi nambani mopondo masani. Kuom kony gochi e %{support_phone}.,END PIN ka at keket suninit.Pin kake walinfakaatin.Qarqars simu dai %{support_phone}.
|
||||
exit_pin_mismatch,"END The new PIN does not match the one you entered. Please try again. For help, call %{support_phone}.",END PIN mpya na udhibitisho wa pin mpya hazilingani. Tafadhali jaribu tena. Kwa usaidizi piga simu %{support_phone}.,END PIN yumbya na uhakikisho wa pin yumbya syivwanene. Tafadhali tata ingi. Kutethwa kuna simu %{support_phone}.,END PIN njeru na pin ya guetekeria shitira hianana . Geria ringi. Kwa uteithio hora thimu %{support_phone}.,END Namba fitse uzdoinjiza kaikara kara na uriyohopya laphiri. Unavoywa ujeze kaheri. Kwa kuavizwa piga simu %{support_phone}.,GIKO. Namba mopondo miketo opogore gi manikuongo keto. Kiyie to ket kendo. Kuom kony gochi e %{support_phone}.,END PIN mpya na udhibitisho wa pin mpya hazilingani. Pin hareti ka at kekeet walinfakan. it dheebi amaale.Qarqars simu dai%{support_phone}.
|
||||
exit_invalid_recipient,"CON Recipient's phone number is not registered or is invalid:
|
||||
00. Retry
|
||||
99. Exit","CON Mpokeaji wa nambari hapatikani au sio sahihi.
|
||||
00. Jaribu tena
|
||||
99. Ondoka","CON Mukwati wa namba ndokwatikana kana ii namba iyaile kana ti sahihi.
|
||||
00. Tata ingi
|
||||
99. Ondoka","CON Mpokeaji wa nambari hapatikani au sio sahihi.Namba ya mutumirwo ndiranyitikana kana ti njega
|
||||
00. Geria ringi
|
||||
99. Uma","CON Muphokezi wa namba kapatikana ama namba kai karakara.
|
||||
00. Jeza Kaheri
|
||||
99. Uka","CON Jayuto mar nambani okyudre kata oknikare.
|
||||
00. Tem kendo
|
||||
99. Wuogi","CON Mpokeaji wa nambari hapatikani au sio sahihi.
|
||||
00.
|
||||
99. Ondoka"
|
||||
exit_successful_transaction,"CON Your request has been sent. %{recipient_information} will receive %{transaction_amount} %{token_symbol} from %{sender_information}.
|
||||
00. Back
|
||||
99. Exit","CON Ombi lako limetumwa. %{recipient_information} atapokea %{transaction_amount} %{token_symbol} kutoka kwa %{sender_information}.
|
||||
00. Rudi
|
||||
99. Ondoka","CON Woni waku niwatumwa. %{recipient_information} nukupokea %{transaction_amount} %{token_symbol} kuma kwa %{sender_information}.
|
||||
00. Itina
|
||||
99. Ondoka","CON Mahoya maku nimatomwo. %{recipient_information} akuamukira%{transaction_amount} %{token_symbol} kuma kwa %{sender_information}.
|
||||
00. Coka
|
||||
99. Uma","CON Mavoyogo gahumwa. %{recipient_information} undaphokera %{transaction_amount} %{token_symbol} kuombola kwa %{sender_information}.
|
||||
00. Uya Nyuma
|
||||
99. Uka","CON Kwayo ni oseor. %{recipient_information} oboyudo %{transaction_amount} %{token_symbol} kowuok kuom %{sender_information}.
|
||||
00. Dog chien
|
||||
99. Wuogi","CON Qarqar kake yaergad. %{recipient_information} inargat %{transaction_amount} %{token_symbol} kutoka kwa %{sender_information}.
|
||||
00. Dheebi
|
||||
99. Bai"
|
||||
exit_insufficient_balance,"CON Payment of %{amount} %{token_symbol} to %{recipient_information} has failed due to insufficient balance.
|
||||
Your Sarafu-Network balances is: %{token_balance}
|
||||
00. Back
|
||||
99. Exit","CON Malipo ya %{amount} %{token_symbol} kwa %{recipient_information} halijakamilika kwa sababu salio lako haitoshi.
|
||||
Akaunti yako ya Sarafu ina salio ifuatayo: %{token_balance}
|
||||
00. Rudi
|
||||
99. Ondoka","CON Ndivi ya %{amount} %{token_symbol} kwa %{recipient_information} inavitukithwa nundu utyalyo waku ni munini.
|
||||
Kinandu chaku cha Sarafu kina utyalo uu: %{token_balance}
|
||||
00. Itina
|
||||
99. Ondoka","CON Marehi ma %{amount} %{token_symbol} kwa %{recipient_information} matinarekereka tondu matigari maku matiraigana.
|
||||
Akaunti yako ya Sarafu ina salio ifuatayo: %{token_balance}
|
||||
00. Coka
|
||||
99. Uma","CON Maripho ga %{amount} %{token_symbol} kwa %{recipient_information} Karidzangwe kukamirika Kwaukala sazoro Karitosha.
|
||||
Akauntiyo vivi ina sazo dza rituwiranavyo: %{token_balance}
|
||||
00. Uya nyuma
|
||||
99. Uka","CON Chudo mar %{amount} %{token_symbol} kuom %{recipient_information} okotieki nikech dong ni okrom.
|
||||
Akaont ni mar Sarafu ni gi dong mar: %{token_balance}
|
||||
00. Dog chien
|
||||
99. Wuogi","CON Malipo ka%{amount} %{token_symbol} kwa %{recipient_information} Inkamilikee balansi kake ingau
|
||||
Akaunti kake balansi akan kabd: %{token_balance}
|
||||
00. Dheebi
|
||||
99. Bai"
|
||||
exit_successful_token_selection,"CON Success! %{token_symbol} is your active Sarafu.
|
||||
00. Back
|
||||
99. Exit","CON Chaguo lako limekamilika, %{token_symbol} ni sarafu itakayotumika.
|
||||
00. Rudi
|
||||
99. Ondoka",,,,,
|
||||
invalid_service_code,Please dial %{valid_service_code} to access Sarafu Network,Bonyeza %{valid_service_code} kutumia mtandao wa Sarafu,Vinyia %{valid_service_code} kutumia mutandao wa Sarafu,Hihinya%{valid_service_code} kuhudhira mutabo wa Sarafu,Hofya %{valid_service_code} Kuhumira Mutandao wa sarafu,Dii %{valid_service_code} mondo iti gi Sarafu,Bonyeza %{valid_service_code} kutumia mtandao wa Sarafu
|
||||
help,"CON For assistance call %{support_phone}
|
||||
00. Back
|
||||
99. Exit","CON Kwa usaidizi piga simu %{support_phone}
|
||||
0. Rudi
|
||||
9. Ondoka","CON Kwa utethyo kuna simu %{support_phone}
|
||||
0. Itina
|
||||
9. Ondoka","CON Kwa uteithio hora thimu %{support_phone}
|
||||
0. Coka
|
||||
9. Uma","CON Kwa Kuavizwa piga simu %{support_phone}
|
||||
0. Uya nyuma
|
||||
9. Uka","CON Kuom kony go simu e %{support_phone}
|
||||
0. Dog chien
|
||||
9. Wuogi","CON Qarqars simu dai%{support_phone}
|
||||
0. Dheebi
|
||||
9. Bai"
|
||||
complete,"CON Your request has been sent. You will receive an SMS shortly.
|
||||
00. Back
|
||||
99. Exit","CON Ombi lako limetumwa. Utapokea uthibitishaji wa SMS kwa muda mfupi.
|
||||
00. Rudi
|
||||
99. Ondoka","CON Woni waku niwatumwa. Nukwata SMS ya kwonya ivinda ite yasa.
|
||||
00. Itina
|
||||
99. Ondoka","CON Mahoya maku nimatomwo. Niukuamukira SMS ya guitikirika ihinda ikuhi .
|
||||
00. Coka
|
||||
99. Uma","CON Vyoyoro rihumwa. Undaphokera Uthibitishaji wa SMS kwa muda mufuhi.
|
||||
00. Uya nyuma
|
||||
99. Uka","CON Kwayo ni oseor. Iboyudo mesej mar ote ni bang' saa matin.
|
||||
00. Dog chien
|
||||
99. Wuogi","CON Qarqars kake yaergad. Utapokea uthibitishaji wa SMS kwa muda mfupi.
|
||||
00. Dheebi
|
||||
99. Bai"
|
||||
account_creation_prompt,END Your account is being created. You will receive an SMS when your account is ready.,END Akaunti yako ya Sarafu inatayarishwa. Utapokea ujumbe wa SMS akaunti yako ikiwa tayari.,END Akaunti yako ya Sarafu yendeye usovwa. Nukwata SMS akaunti yaku yasovwa.,END Akaunti yaku ya Sarafu niiraharirio.Niugutumirwo SMS akauti yaku ya rikio kuharirio,END Akauntiyo ya sarafu idzikoni. Undaphokera ujumbe wa SMS ichikala tayari.,END Akaont ni mar Sarafu iloso. Iboyudo mesej ka akaont ni otieki.,END Akaunti yako ya Sarafu inatayarishwa. Utapokea ujumbe wa SMS akaunti yako ikiwa tayari.
|
||||
initial_middle_language_set,"CON Choose language:
|
||||
%{middle_language_set}
|
||||
|
||||
11. Next
|
||||
22. Previous
|
||||
00. Exit","CON Chagua lugha:
|
||||
%{middle_language_set}
|
||||
|
||||
11. Mbele
|
||||
22. Rudi
|
||||
00. Ondoka",,,,,
|
||||
initial_last_language_set,"CON Choose language:
|
||||
%{last_language_set}
|
||||
|
||||
22. Previous
|
||||
00. Exit","CON Choose language:
|
||||
%{last_language_set}
|
||||
|
||||
22. Rudi
|
||||
00. Ondoka",,,,,
|
||||
middle_language_set,"CON Choose language:
|
||||
%{middle_language_set}
|
||||
|
||||
11. Next
|
||||
22. Previous
|
||||
00. Exit","CON Chagua lugha:
|
||||
%{middle_language_set}
|
||||
|
||||
11. Mbele
|
||||
22. Rudi
|
||||
00. Ondoka",,,,,
|
||||
last_language_set,"CON Choose language:
|
||||
|
||||
%{last_language_set}
|
||||
22. Previous
|
||||
00. Exit","CON Choose language:
|
||||
%{last_language_set}
|
||||
|
||||
22. Rudi
|
||||
00. Ondoka",,,,,
|
|
52
docker-compose.build.yml
Normal file
52
docker-compose.build.yml
Normal file
@ -0,0 +1,52 @@
|
||||
version: '3.2'
|
||||
|
||||
services:
|
||||
cic-cache:
|
||||
image: ${IMAGE_BASE_URL:-registry.gitlab.com/grassrootseconomics/cic-internal-integration}/cic-cache:${TAG:-latest}
|
||||
build:
|
||||
context: apps/cic-cache
|
||||
dockerfile: docker/Dockerfile
|
||||
cache_from:
|
||||
- ${IMAGE_BASE_URL:-registry.gitlab.com/grassrootseconomics/cic-internal-integration}/cic-cache:latest
|
||||
cic-eth:
|
||||
image: ${IMAGE_BASE_URL:-registry.gitlab.com/grassrootseconomics/cic-internal-integration}/cic-eth:${TAG:-latest}
|
||||
build:
|
||||
context: apps/cic-eth
|
||||
dockerfile: docker/Dockerfile
|
||||
cache_from:
|
||||
- ${IMAGE_BASE_URL:-registry.gitlab.com/grassrootseconomics/cic-internal-integration}/cic-eth:latest
|
||||
cic-meta:
|
||||
image: ${IMAGE_BASE_URL:-registry.gitlab.com/grassrootseconomics/cic-internal-integration}/cic-meta:${TAG:-latest}
|
||||
build:
|
||||
context: apps/cic-meta
|
||||
dockerfile: docker/Dockerfile
|
||||
cache_from:
|
||||
- ${IMAGE_BASE_URL:-registry.gitlab.com/grassrootseconomics/cic-internal-integration}/cic-meta:latest
|
||||
cic-notify:
|
||||
image: ${IMAGE_BASE_URL:-registry.gitlab.com/grassrootseconomics/cic-internal-integration}/cic-notify:${TAG:-latest}
|
||||
build:
|
||||
context: apps/cic-notify
|
||||
dockerfile: docker/Dockerfile
|
||||
cache_from:
|
||||
- ${IMAGE_BASE_URL:-registry.gitlab.com/grassrootseconomics/cic-internal-integration}/cic-notify:latest
|
||||
funga-eth:
|
||||
image: ${IMAGE_BASE_URL:-registry.gitlab.com/grassrootseconomics/cic-internal-integration}/funga-eth:${TAG:-latest}
|
||||
build:
|
||||
context: apps/cic-signer
|
||||
dockerfile: Dockerfile
|
||||
cache_from:
|
||||
- ${IMAGE_BASE_URL:-registry.gitlab.com/grassrootseconomics/cic-internal-integration}/funga-eth:latest
|
||||
cic-ussd:
|
||||
image: ${IMAGE_BASE_URL:-registry.gitlab.com/grassrootseconomics/cic-internal-integration}/cic-ussd:${TAG:-latest}
|
||||
build:
|
||||
context: apps/cic-ussd
|
||||
dockerfile: docker/Dockerfile
|
||||
cache_from:
|
||||
- ${IMAGE_BASE_URL:-registry.gitlab.com/grassrootseconomics/cic-internal-integration}/cic-ussd:latest
|
||||
cic-contract-bootstrap:
|
||||
image: ${IMAGE_BASE_URL:-registry.gitlab.com/grassrootseconomics/cic-internal-integration}/cic-contract-bootstrap:${TAG:-latest}
|
||||
build:
|
||||
context: apps/contract-migration
|
||||
dockerfile: docker/Dockerfile
|
||||
cache_from:
|
||||
- ${IMAGE_BASE_URL:-registry.gitlab.com/grassrootseconomics/cic-internal-integration}/cic-ussd:latest
|
23
scripts/local-build-push.sh
Normal file
23
scripts/local-build-push.sh
Normal file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# dependencies:
|
||||
# - docker-compose >= v1.25.0
|
||||
# - sbot >= v1.0.0.
|
||||
|
||||
set -e
|
||||
|
||||
export DOCKER_BUILDKIT=1
|
||||
export COMPOSE_DOCKER_CLI_BUILD=1
|
||||
|
||||
PREDICTED_TAG=$(sbot predict version -m auto)
|
||||
|
||||
docker-compose -f docker-compose.build.yml build --progress plain
|
||||
|
||||
export TAG=v$PREDICTED_TAG
|
||||
|
||||
docker-compose -f docker-compose.build.yml build --progress plain
|
||||
docker-compose -f docker-compose.build.yml push
|
||||
|
||||
export TAG=latest
|
||||
|
||||
docker-compose -f docker-compose.build.yml push
|
Loading…
Reference in New Issue
Block a user