Remove submodule cic ussd
This commit is contained in:
115
apps/cic-ussd/tests/fixtures/callback.py
vendored
Normal file
115
apps/cic-ussd/tests/fixtures/callback.py
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
# standard imports
|
||||
import json
|
||||
|
||||
# third party imports
|
||||
import pytest
|
||||
|
||||
# local imports
|
||||
from cic_ussd.redis import InMemoryStore
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def account_creation_action_data():
|
||||
return {
|
||||
'phone_number': '+254712345678',
|
||||
'sms_notification_sent': False,
|
||||
'status': 'PENDING',
|
||||
'task_id': '31e85315-feee-4b6d-995e-223569082cc4'
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def set_account_creation_action_data(init_redis_cache, account_creation_action_data):
|
||||
redis_cache = init_redis_cache
|
||||
action_data = account_creation_action_data
|
||||
task_id = action_data.get('task_id')
|
||||
redis_cache.set(task_id, json.dumps(action_data))
|
||||
redis_cache.persist(task_id)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def successful_incoming_token_gift_callback():
|
||||
return {
|
||||
'RESULT': {
|
||||
'hash': '0xb469fb2ebacc9574afb7b51d44e174fba7129fde71bf757fd39784363270832b',
|
||||
'sender': '0xd6204101012270Bf2558EDcFEd595938d1847bf0',
|
||||
'recipient': '0xFD9c5aD15C72C6F60f1a119A608931226674243f',
|
||||
'source_value': 1048576,
|
||||
'destination_value': 1048576,
|
||||
'source_token': '0xa75B519dc9b0A50D267E03D8B6808f85A66932dd',
|
||||
'destination_token': '0xa75B519dc9b0A50D267E03D8B6808f85A66932dd',
|
||||
'source_token_symbol': 'SRF',
|
||||
'destination_token_symbol': 'SRF',
|
||||
'source_token_decimals': 18,
|
||||
'destination_token_decimals': 18,
|
||||
'chain': 'Bloxberg:8996'
|
||||
},
|
||||
'PARAM': 'tokengift',
|
||||
'STATUS_CODE': 0,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def successful_incoming_transfer_callback():
|
||||
return {
|
||||
'RESULT': {
|
||||
'hash': '0x8b0ed32533164d010afc46c0011fbcb58b0198e03c05b96e2791555746bd3606',
|
||||
'sender': '0xd6204101012270Bf2558EDcFEd595938d1847bf1',
|
||||
'recipient': '0xd6204101012270Bf2558EDcFEd595938d1847bf0',
|
||||
'source_value': 10000000000000000000000,
|
||||
'destination_value': 10000000000000000000000,
|
||||
'source_token': '0xa75B519dc9b0A50D267E03D8B6808f85A66932dd',
|
||||
'destination_token': '0xa75B519dc9b0A50D267E03D8B6808f85A66932dd',
|
||||
'source_token_symbol': 'SRF',
|
||||
'destination_token_symbol': 'SRF',
|
||||
'source_token_decimals': 18,
|
||||
'destination_token_decimals': 18,
|
||||
'chain': 'Bloxberg:8996'
|
||||
},
|
||||
'PARAM': 'transfer',
|
||||
'STATUS_CODE': 0
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def incoming_transfer_callback_invalid_tx_status_code():
|
||||
return {
|
||||
'RESULT': {
|
||||
'hash': '0x8b0ed32533164d010afc46c0011fbcb58b0198e03c05b96e2791555746bd3606',
|
||||
'sender': '0xd6204101012270Bf2558EDcFEd595938d1847bf1',
|
||||
'recipient': '0xd6204101012270Bf2558EDcFEd595938d1847bf0',
|
||||
'source_value': 10000000000000000000000,
|
||||
'destination_value': 10000000000000000000000,
|
||||
'source_token': '0xa75B519dc9b0A50D267E03D8B6808f85A66932dd',
|
||||
'destination_token': '0xa75B519dc9b0A50D267E03D8B6808f85A66932dd',
|
||||
'source_token_symbol': 'SRF',
|
||||
'destination_token_symbol': 'SRF',
|
||||
'source_token_decimals': 18,
|
||||
'destination_token_decimals': 18,
|
||||
'chain': 'Bloxberg:8996'
|
||||
},
|
||||
'PARAM': 'transfer',
|
||||
'STATUS_CODE': 1
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def incoming_transfer_callback_invalid_tx_param():
|
||||
return {
|
||||
'RESULT': {
|
||||
'hash': '0x8b0ed32533164d010afc46c0011fbcb58b0198e03c05b96e2791555746bd3606',
|
||||
'sender': '0xd6204101012270Bf2558EDcFEd595938d1847bf1',
|
||||
'recipient': '0xd6204101012270Bf2558EDcFEd595938d1847bf0',
|
||||
'source_value': 10000000000000000000000,
|
||||
'destination_value': 10000000000000000000000,
|
||||
'source_token': '0xa75B519dc9b0A50D267E03D8B6808f85A66932dd',
|
||||
'destination_token': '0xa75B519dc9b0A50D267E03D8B6808f85A66932dd',
|
||||
'source_token_symbol': 'SRF',
|
||||
'destination_token_symbol': 'SRF',
|
||||
'source_token_decimals': 18,
|
||||
'destination_token_decimals': 18,
|
||||
'chain': 'Bloxberg:8996'
|
||||
},
|
||||
'PARAM': 'erroneousparam',
|
||||
'STATUS_CODE': 0
|
||||
}
|
||||
43
apps/cic-ussd/tests/fixtures/celery.py
vendored
Normal file
43
apps/cic-ussd/tests/fixtures/celery.py
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# standard imports
|
||||
import logging
|
||||
import pytest
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def celery_includes():
|
||||
return [
|
||||
'cic_ussd.tasks.ussd',
|
||||
'cic_ussd.tasks.callback_handler',
|
||||
'cic_notify.tasks.sms'
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def celery_config():
|
||||
bq = tempfile.mkdtemp()
|
||||
bp = tempfile.mkdtemp()
|
||||
rq = tempfile.mkdtemp()
|
||||
logg.debug('celery broker queue {} processed {}'.format(bq, bp))
|
||||
logg.debug('celery backend store {}'.format(rq))
|
||||
yield {
|
||||
'broker_url': 'filesystem://',
|
||||
'broker_transport_options': {
|
||||
'data_folder_in': bq,
|
||||
'data_folder_out': bq,
|
||||
'data_folder_processed': bp,
|
||||
},
|
||||
'result_backend': 'file://{}'.format(rq),
|
||||
}
|
||||
logg.debug('cleaning up celery filesystem backend files {} {} {}'.format(bq, bp, rq))
|
||||
shutil.rmtree(bq)
|
||||
shutil.rmtree(bp)
|
||||
shutil.rmtree(rq)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def celery_enable_logging():
|
||||
return True
|
||||
104
apps/cic-ussd/tests/fixtures/config.py
vendored
Normal file
104
apps/cic-ussd/tests/fixtures/config.py
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
# standard imports
|
||||
import i18n
|
||||
import logging
|
||||
import os
|
||||
|
||||
# third party imports
|
||||
import pytest
|
||||
from confini import Config
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db import dsn_from_config
|
||||
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.state_machine import UssdStateMachine
|
||||
from cic_ussd.encoder import PasswordEncoder
|
||||
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
fixtures_dir = os.path.dirname(__file__)
|
||||
root_directory = os.path.dirname(os.path.dirname(fixtures_dir))
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def load_config():
|
||||
config_directory = os.path.join(root_directory, '.config/test')
|
||||
config = Config(config_dir=config_directory)
|
||||
config.process(set_as_current=True)
|
||||
logg.debug('config loaded\n{}'.format(config))
|
||||
return config
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def alembic_config():
|
||||
migrations_directory = os.path.join(root_directory, 'cic_ussd', 'db', 'migrations', 'default')
|
||||
file = os.path.join(migrations_directory, 'alembic.ini')
|
||||
return {
|
||||
'file': file,
|
||||
'script_location': migrations_directory
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def alembic_engine(load_config):
|
||||
data_source_name = dsn_from_config(load_config)
|
||||
database_engine = create_engine(data_source_name)
|
||||
return database_engine
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def set_fernet_key(load_config):
|
||||
PasswordEncoder.set_key(load_config.get('APP_PASSWORD_PEPPER'))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def set_locale_files(load_config):
|
||||
i18n.load_path.append(load_config.get('APP_LOCALE_PATH'))
|
||||
i18n.set('fallback', load_config.get('APP_LOCALE_FALLBACK'))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def load_ussd_menu(load_config):
|
||||
ussd_menu_db = create_local_file_data_stores(file_location=load_config.get('USSD_MENU_FILE'), table_name="ussd_menu")
|
||||
UssdMenu.ussd_menu_db = ussd_menu_db
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def load_data_into_state_machine(load_config):
|
||||
UssdStateMachine.states = json_file_parser(filepath=load_config.get('STATEMACHINE_STATES'))
|
||||
UssdStateMachine.transitions = json_file_parser(filepath=load_config.get('STATEMACHINE_TRANSITIONS'))
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def uwsgi_env():
|
||||
return {
|
||||
'REQUEST_METHOD': 'POST',
|
||||
'REQUEST_URI': '/',
|
||||
'PATH_INFO': '/',
|
||||
'QUERY_STRING': '',
|
||||
'SERVER_PROTOCOL': 'HTTP/1.1',
|
||||
'SCRIPT_NAME': '',
|
||||
'SERVER_NAME': 'mango-habanero',
|
||||
'SERVER_PORT': '9091',
|
||||
'UWSGI_ROUTER': 'http',
|
||||
'REMOTE_ADDR': '127.0.0.1',
|
||||
'REMOTE_PORT': '33515',
|
||||
'CONTENT_TYPE': 'application/json',
|
||||
'HTTP_USER_AGENT': 'PostmanRuntime/7.26.8',
|
||||
'HTTP_ACCEPT': '*/*',
|
||||
'HTTP_POSTMAN_TOKEN': 'c1f6eb29-8160-497f-a5a1-935d175e2eb7',
|
||||
'HTTP_HOST': '127.0.0.1:9091',
|
||||
'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br',
|
||||
'HTTP_CONNECTION': 'keep-alive',
|
||||
'CONTENT_LENGTH': '102',
|
||||
'wsgi.version': (1, 0),
|
||||
'wsgi.run_once': False,
|
||||
'wsgi.multithread': False,
|
||||
'wsgi.multiprocess': False,
|
||||
'wsgi.url_scheme': 'http',
|
||||
'uwsgi.version': b'2.0.19.1',
|
||||
'uwsgi.node': b'mango-habanero'
|
||||
}
|
||||
|
||||
44
apps/cic-ussd/tests/fixtures/db.py
vendored
Normal file
44
apps/cic-ussd/tests/fixtures/db.py
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
# standard imports
|
||||
import os
|
||||
|
||||
# third party imports
|
||||
import alembic
|
||||
import pytest
|
||||
from alembic.config import Config as AlembicConfig
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db import dsn_from_config
|
||||
from cic_ussd.db.models.base import SessionBase
|
||||
from tests.fixtures.config import root_directory
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def database_engine(load_config):
|
||||
data_source_name = dsn_from_config(load_config)
|
||||
SessionBase.connect(data_source_name)
|
||||
yield data_source_name
|
||||
if load_config.get('DATABASE_ENGINE') == 'sqlite':
|
||||
os.unlink(load_config.get('DATABASE_NAME'))
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def init_database(load_config, database_engine):
|
||||
db_directory = os.path.join(root_directory, 'cic_ussd', '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')
|
||||
|
||||
SessionBase.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 SessionBase.session
|
||||
|
||||
SessionBase.session.commit()
|
||||
SessionBase.session.close()
|
||||
|
||||
51
apps/cic-ussd/tests/fixtures/mocks.py
vendored
Normal file
51
apps/cic-ussd/tests/fixtures/mocks.py
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
# standard imports
|
||||
|
||||
# third-party imports
|
||||
import pytest
|
||||
|
||||
# local imports
|
||||
from cic_ussd.translation import translation_for
|
||||
from cic_ussd.transactions import truncate
|
||||
|
||||
|
||||
@pytest.fixture(scope='function', autouse=True)
|
||||
def mock_notifier_api(mocker):
|
||||
messages = []
|
||||
|
||||
def mock_sms_api(self, message: str, recipient: str):
|
||||
pass
|
||||
|
||||
def mock_send_sms_notification(self, key: str, phone_number: str, preferred_language: str, **kwargs):
|
||||
message = translation_for(key=key, preferred_language=preferred_language, **kwargs)
|
||||
messages.append({'message': message, 'recipient': phone_number})
|
||||
|
||||
mocker.patch('cic_notify.api.Api.sms', mock_sms_api)
|
||||
mocker.patch('cic_ussd.notifications.Notifier.send_sms_notification', mock_send_sms_notification)
|
||||
return messages
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_outgoing_transactions(mocker):
|
||||
transactions = []
|
||||
|
||||
def mock_process_outgoing_transfer_transaction(self, amount: int, token_symbol: str = 'SRF'):
|
||||
transactions.append({
|
||||
'amount': amount,
|
||||
'token_symbol': token_symbol
|
||||
})
|
||||
|
||||
mocker.patch(
|
||||
'cic_ussd.transactions.OutgoingTransactionProcessor.process_outgoing_transfer_transaction',
|
||||
mock_process_outgoing_transfer_transaction
|
||||
)
|
||||
return transactions
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_balance(mocker):
|
||||
mocked_operational_balance = mocker.patch('cic_ussd.accounts.BalanceManager.get_operational_balance')
|
||||
|
||||
def _mock_operational_balance(balance: int):
|
||||
mocked_operational_balance.return_value = truncate(value=balance, decimals=2)
|
||||
|
||||
return _mock_operational_balance
|
||||
11
apps/cic-ussd/tests/fixtures/redis.py
vendored
Normal file
11
apps/cic-ussd/tests/fixtures/redis.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# third party imports
|
||||
import pytest
|
||||
|
||||
# local imports
|
||||
from cic_ussd.redis import InMemoryStore
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def init_redis_cache(redisdb):
|
||||
InMemoryStore.cache = redisdb
|
||||
return redisdb
|
||||
20
apps/cic-ussd/tests/fixtures/requests.py
vendored
Normal file
20
apps/cic-ussd/tests/fixtures/requests.py
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# third party imports
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def valid_locked_accounts_env(uwsgi_env):
|
||||
env = uwsgi_env
|
||||
env['REQUEST_METHOD'] = 'GET'
|
||||
env['PATH_INFO'] = '/accounts/locked/10/10'
|
||||
|
||||
return env
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def get_request_with_params_env(uwsgi_env):
|
||||
env = uwsgi_env
|
||||
env['REQUEST_METHOD'] = 'GET'
|
||||
env['REQUEST_URI'] = '/?phone=0700000000'
|
||||
|
||||
return env
|
||||
94
apps/cic-ussd/tests/fixtures/user.py
vendored
Normal file
94
apps/cic-ussd/tests/fixtures/user.py
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
# standard imports
|
||||
from random import randint
|
||||
import uuid
|
||||
|
||||
# third party imports
|
||||
import pytest
|
||||
from faker import Faker
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.user import AccountStatus, User
|
||||
|
||||
|
||||
fake = Faker()
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_activated_user(init_database, set_fernet_key):
|
||||
user = User(
|
||||
blockchain_address='0xFD9c5aD15C72C6F60f1a119A608931226674243f',
|
||||
phone_number='+25498765432'
|
||||
)
|
||||
user.preferred_language = 'en'
|
||||
user.create_password('0000')
|
||||
user.activate_account()
|
||||
init_database.add(user)
|
||||
init_database.commit()
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_valid_tx_recipient(init_database, set_fernet_key):
|
||||
user = User(
|
||||
blockchain_address='0xd6204101012270Bf2558EDcFEd595938d1847bf0',
|
||||
phone_number='+25498765432'
|
||||
)
|
||||
user.preferred_language = 'en'
|
||||
user.create_password('0000')
|
||||
user.activate_account()
|
||||
init_database.add(user)
|
||||
init_database.commit()
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_valid_tx_sender(init_database, set_fernet_key):
|
||||
user = User(
|
||||
blockchain_address='0xd6204101012270Bf2558EDcFEd595938d1847bf1',
|
||||
phone_number='+25498765433'
|
||||
)
|
||||
user.preferred_language = 'en'
|
||||
user.create_password('0000')
|
||||
user.activate_account()
|
||||
init_database.add(user)
|
||||
init_database.commit()
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_pending_user(init_database, set_fernet_key):
|
||||
user = User(
|
||||
blockchain_address='0x0ebdea8612c1b05d952c036859266c7f2cfcd6a29842d9c6cce3b9f1ba427588',
|
||||
phone_number='+25498765432'
|
||||
)
|
||||
init_database.add(user)
|
||||
init_database.commit()
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_pin_blocked_user(init_database, set_fernet_key):
|
||||
user = User(
|
||||
blockchain_address='0x0ebdea8612c1b05d952c036859266c7f2cfcd6a29842d9c6cce3b9f1ba427588',
|
||||
phone_number='+25498765432'
|
||||
)
|
||||
user.create_password('0000')
|
||||
user.failed_pin_attempts = 3
|
||||
user.account_status = 3
|
||||
init_database.add(user)
|
||||
init_database.commit()
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_locked_accounts(init_database, set_fernet_key):
|
||||
for i in range(20):
|
||||
blockchain_address = str(uuid.uuid4())
|
||||
phone_number = fake.phone_number()
|
||||
pin = f'{randint(1000, 9999)}'
|
||||
user = User(phone_number=phone_number, blockchain_address=blockchain_address)
|
||||
user.create_password(password=pin)
|
||||
user.failed_pin_attempts = 3
|
||||
user.account_status = AccountStatus.LOCKED.value
|
||||
user.session.add(user)
|
||||
user.session.commit()
|
||||
52
apps/cic-ussd/tests/fixtures/ussd_session.py
vendored
Normal file
52
apps/cic-ussd/tests/fixtures/ussd_session.py
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
# standard imports
|
||||
import json
|
||||
|
||||
# third-party imports
|
||||
import pytest
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.ussd_session import UssdSession
|
||||
from cic_ussd.redis import InMemoryStore
|
||||
from cic_ussd.session.ussd_session import UssdSession as InMemoryUssdSession
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def ussd_session_data():
|
||||
return {
|
||||
'external_session_id': 'AT974186',
|
||||
'service_code': '*483*46#',
|
||||
'msisdn': '+25498765432',
|
||||
'user_input': '1',
|
||||
'state': 'initial_language_selection',
|
||||
'session_data': {},
|
||||
'version': 2
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_in_redis_ussd_session(ussd_session_data, init_redis_cache):
|
||||
external_session_id = ussd_session_data.get('external_session_id')
|
||||
InMemoryUssdSession.redis_cache = InMemoryStore.cache
|
||||
InMemoryUssdSession.redis_cache.set(external_session_id, json.dumps(ussd_session_data))
|
||||
return InMemoryUssdSession.redis_cache
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def get_in_redis_ussd_session(ussd_session_data, create_in_redis_ussd_session):
|
||||
external_session_id = ussd_session_data.get('external_session_id')
|
||||
ussd_session_data = create_in_redis_ussd_session.get(external_session_id)
|
||||
ussd_session_data = json.loads(ussd_session_data)
|
||||
# remove version from ussd_session data because the ussd_session object does not expect a version at initialization
|
||||
del ussd_session_data['version']
|
||||
ussd_session = InMemoryUssdSession(**{key: value for key, value in ussd_session_data.items()})
|
||||
ussd_session.version = ussd_session_data.get('version')
|
||||
return ussd_session
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_in_db_ussd_session(init_database, ussd_session_data):
|
||||
ussd_session_data['session_data'] = {}
|
||||
ussd_session = UssdSession(**{key: value for key, value in ussd_session_data.items()})
|
||||
init_database.add(ussd_session)
|
||||
init_database.commit()
|
||||
return ussd_session
|
||||
Reference in New Issue
Block a user