Compare commits
135 Commits
cic-eth-se
...
lash/impro
| Author | SHA1 | Date | |
|---|---|---|---|
|
004e30af31
|
|||
|
|
7618abcda3
|
||
|
|
94d8ddb164
|
||
|
|
7f958d4be8
|
||
|
|
03b06ca8c1 | ||
|
|
14449f5c6d
|
||
|
|
15618fa061
|
||
|
|
3a52a78e93
|
||
|
|
6562d37a30
|
||
|
|
c5efa56885
|
||
|
d6346bb87b
|
|||
|
9050d331cd
|
|||
|
99997df248
|
|||
|
d04a4bf5c6
|
|||
|
43c49dd527
|
|||
|
511557c242
|
|||
|
887799962f
|
|||
|
3acc3cf417
|
|||
|
ceeb246ce2
|
|||
|
45499ec839
|
|||
|
77bdee049c
|
|||
|
|
0cf6489f49 | ||
|
|
31256b3650
|
||
|
|
380550cb84
|
||
|
|
a356585c6a
|
||
|
|
4809bc8c22
|
||
|
|
760f618943
|
||
|
|
39de1837c2
|
||
|
|
97e45c87d7
|
||
|
|
4658a5d8e5
|
||
|
|
995d4e0bd0
|
||
|
|
140b72a72b
|
||
|
|
21b0c4a48b
|
||
|
|
0b66462c11
|
||
|
|
f18f865231
|
||
|
|
ad1c241a85
|
||
|
|
99b0fb5aed | ||
|
|
29423449b7 | ||
|
|
58e766aa58
|
||
|
|
2ebcd3e3de | ||
|
|
c440b049cc
|
||
|
|
09034af5bc
|
||
|
|
dc80bae673
|
||
|
|
d88ae00b72
|
||
|
|
7a366edb9d
|
||
|
|
0b912b99b6
|
||
|
|
cbd4aef004
|
||
|
|
6f7f91780b
|
||
|
|
83ecdaf023
|
||
|
|
e2ef9b43c8
|
||
|
|
6e58e4e4de
|
||
|
|
f46c9b0e7d | ||
|
|
6ca3fd55d7
|
||
|
|
258ed420b8 | ||
|
|
1c022e9853
|
||
|
|
d35e144723
|
||
|
|
fb953d0318
|
||
|
|
858bbdb69a | ||
|
|
66e23e4e20
|
||
|
|
546256c86a
|
||
|
|
d9720bd0aa | ||
|
|
e9e9f66d97
|
||
|
|
0d640fab57 | ||
|
|
4ce85bc824
|
||
|
|
ce67f83457
|
||
|
|
13f2e17931
|
||
|
|
f236234682 | ||
|
|
1f37632f0f
|
||
|
|
03d7518f8c | ||
|
|
67152d0df1
|
||
|
9168322941
|
|||
|
2fbd338e24
|
|||
|
c7d7f2a64d
|
|||
|
16153df2f0
|
|||
|
|
4391fa3aff | ||
|
|
7ce68021bd | ||
|
|
cd602dee49
|
||
|
|
a548ba6fce
|
||
|
|
a6de7e9fe0 | ||
|
|
e705a94873
|
||
|
|
3923de0a81
|
||
|
|
5c0250b5b9
|
||
|
|
3285d8dfe5
|
||
|
|
9d349f1579
|
||
|
|
837a1770d1
|
||
|
003febec9d
|
|||
|
f066a32ce8
|
|||
|
|
ad493705ad
|
||
|
|
b765c4ab88
|
||
|
|
e4935d3b58 | ||
|
|
f88f0e321b
|
||
|
31fa721397
|
|||
|
16481da193
|
|||
|
97a48cd8c6
|
|||
|
|
7732412341 | ||
|
|
649b124a61
|
||
|
7601e3eeff
|
|||
|
60a9efc88b
|
|||
|
45011b58c4
|
|||
|
|
f1a0b4ee7c | ||
|
|
c57abb7ad5
|
||
|
930a99c974
|
|||
|
b0935caab8
|
|||
|
|
bdd5f6fcec
|
||
|
|
a293c2460e
|
||
|
|
0ee6400d7d
|
||
|
|
677fb346fd
|
||
|
|
ea3c75e755
|
||
|
|
0b2f22c416
|
||
|
|
24385ea27d
|
||
|
|
9a154a8046
|
||
|
|
d3576c8ec7
|
||
|
|
79ee2bf4ff
|
||
|
|
89ac70371a
|
||
|
|
5ea0318b0b
|
||
|
|
5dfb96ec0c
|
||
|
|
4634ac41df | ||
|
|
97f4fe8ca7
|
||
|
|
b36529f7fa
|
||
|
|
a6675f2348
|
||
|
|
e3116d74d6
|
||
|
|
c0bbdc9bec
|
||
|
|
396bd4f300
|
||
|
|
58547b4067
|
||
|
|
9009815d78
|
||
|
|
2da19f5819
|
||
|
|
3948d5aa40
|
||
|
|
ed432abb23
|
||
|
|
f251b8b729
|
||
|
|
36e791e08a
|
||
|
|
71a7e3d3d5
|
||
|
|
335b7b30a4
|
||
|
|
3b1f470ddf
|
||
|
|
4c9f20aa7f
|
||
|
|
980191be4f
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,4 +15,3 @@ build/
|
||||
.idea
|
||||
**/.vim
|
||||
**/*secret.yaml
|
||||
.envrc
|
||||
@@ -1,36 +1 @@
|
||||
# CIC-ETH
|
||||
|
||||
## Testing CIC-ETH locally.
|
||||
|
||||
### Setup a Virtual Env
|
||||
|
||||
```bash
|
||||
python3 -m venv ./venv # Python 3.9
|
||||
source ./venv/activate
|
||||
```
|
||||
|
||||
### Running All Unit Tests
|
||||
|
||||
```bash
|
||||
bash ./tests/run_tests.sh # This will also install required dependencies
|
||||
```
|
||||
|
||||
### Running Specific Unit Tests
|
||||
|
||||
Ensure that:
|
||||
|
||||
- You have called `bash ./tests/run_tests.sh` at least once or run the following to install required dependencies
|
||||
- You have activated the virtual environment
|
||||
|
||||
```
|
||||
pip install --extra-index-url https://pip.grassrootseconomics.net --extra-index-url https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple \
|
||||
-r admin_requirements.txt \
|
||||
-r services_requirements.txt \
|
||||
-r test_requirements.txt
|
||||
```
|
||||
|
||||
Then here is an example that only runs tests with the keyword(-k) `test_server`
|
||||
|
||||
```bash
|
||||
pytest -s -v --log-cli-level DEBUG --log-level DEBUG -k test_server
|
||||
```
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
# standard imports
|
||||
import logging
|
||||
|
||||
# external imports
|
||||
# external imports
|
||||
import celery
|
||||
from chainlib.chain import ChainSpec
|
||||
from hexathon import strip_0x
|
||||
|
||||
# local imports
|
||||
from cic_eth.api.base import ApiBase
|
||||
from cic_eth.enum import LockEnum
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# standard imports
|
||||
import os
|
||||
import random
|
||||
import uuid
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def blockchain_address() -> str:
|
||||
return os.urandom(20).hex().lower()
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
# standard imports
|
||||
import os
|
||||
|
||||
# external imports
|
||||
import pytest
|
||||
from celery import uuid
|
||||
# test imports
|
||||
from cic_eth.pytest.helpers.accounts import blockchain_address
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def task_uuid():
|
||||
return uuid()
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def default_token_data(foo_token_symbol, foo_token):
|
||||
return {
|
||||
'symbol': foo_token_symbol,
|
||||
'address': foo_token,
|
||||
'name': 'Giftable Token',
|
||||
'decimals': 6,
|
||||
"converters": []
|
||||
}
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_account_creation_task_request(mocker, task_uuid):
|
||||
def mock_request(self):
|
||||
mocked_task_request = mocker.patch('celery.app.task.Task.request')
|
||||
mocked_task_request.id = task_uuid
|
||||
return mocked_task_request
|
||||
mocker.patch('cic_eth.api.api_task.Api.create_account', mock_request)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_account_creation_task_result(mocker, task_uuid):
|
||||
def task_result(self):
|
||||
sync_res = mocker.patch('celery.result.AsyncResult')
|
||||
sync_res.id = task_uuid
|
||||
sync_res.get.return_value = blockchain_address()
|
||||
return sync_res
|
||||
mocker.patch('cic_eth.api.api_task.Api.create_account', task_result)
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_token_api_query(foo_token_symbol, foo_token, mocker, task_uuid):
|
||||
def mock_query(self, token_symbol, proof=None):
|
||||
sync_res = mocker.patch('celery.result.AsyncResult')
|
||||
sync_res.id = task_uuid
|
||||
sync_res.get.return_value = [
|
||||
{
|
||||
'address': foo_token,
|
||||
'converters': [],
|
||||
'decimals': 6,
|
||||
'name': 'Giftable Token',
|
||||
'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'],
|
||||
'symbol': foo_token_symbol,
|
||||
},{'5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C','Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']}
|
||||
]
|
||||
return sync_res
|
||||
mocker.patch('cic_eth.api.api_task.Api.token', mock_query)
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_tokens_api_query(foo_token_symbol, foo_token, mocker, task_uuid):
|
||||
def mock_query(self, token_symbol, proof=None):
|
||||
sync_res = mocker.patch('celery.result.AsyncResult')
|
||||
sync_res.id = task_uuid
|
||||
sync_res.get.return_value = [[
|
||||
{
|
||||
'address': foo_token,
|
||||
'converters': [],
|
||||
'decimals': 6,
|
||||
'name': 'Giftable Token',
|
||||
'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'],
|
||||
'symbol': foo_token_symbol,
|
||||
},{'5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C','Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']}
|
||||
], [
|
||||
{
|
||||
'address': foo_token,
|
||||
'converters': [],
|
||||
'decimals': 6,
|
||||
'name': 'Giftable Token',
|
||||
'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'],
|
||||
'symbol': foo_token_symbol,
|
||||
},{'5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C','Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']}
|
||||
]]
|
||||
return sync_res
|
||||
mocker.patch('cic_eth.api.api_task.Api.tokens', mock_query)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_sync_balance_api_query(balances, mocker, task_uuid):
|
||||
def sync_api_query(self, address: str, token_symbol: str):
|
||||
sync_res = mocker.patch('celery.result.AsyncResult')
|
||||
sync_res.id = task_uuid
|
||||
sync_res.get.return_value = balances
|
||||
return sync_res
|
||||
mocker.patch('cic_eth.api.api_task.Api.balance', sync_api_query)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_sync_default_token_api_query(default_token_data, mocker, task_uuid):
|
||||
def mock_query(self):
|
||||
sync_res = mocker.patch('celery.result.AsyncResult')
|
||||
sync_res.id = task_uuid
|
||||
sync_res.get.return_value = default_token_data
|
||||
return sync_res
|
||||
mocker.patch('cic_eth.api.api_task.Api.default_token', mock_query)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_transaction_list_query(mocker):
|
||||
query_args = {}
|
||||
|
||||
def mock_query(self, address: str, limit: int):
|
||||
query_args['address'] = address
|
||||
query_args['limit'] = limit
|
||||
|
||||
mocker.patch('cic_eth.api.api_task.Api.list', mock_query)
|
||||
return query_args
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_transfer_api(mocker):
|
||||
transfer_args = {}
|
||||
|
||||
def mock_transfer(self, from_address: str, to_address: str, value: int, token_symbol: str):
|
||||
transfer_args['from_address'] = from_address
|
||||
transfer_args['to_address'] = to_address
|
||||
transfer_args['value'] = value
|
||||
transfer_args['token_symbol'] = token_symbol
|
||||
|
||||
mocker.patch('cic_eth.api.api_task.Api.transfer', mock_transfer)
|
||||
return transfer_args
|
||||
@@ -1,11 +1,15 @@
|
||||
#!/usr/bin/python
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
import uuid
|
||||
import json
|
||||
import argparse
|
||||
|
||||
# external imports
|
||||
import redis
|
||||
from xdg.BaseDirectory import xdg_config_home
|
||||
from chainlib.chain import ChainSpec
|
||||
|
||||
# local imports
|
||||
import cic_eth.cli
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import logging
|
||||
|
||||
import cic_eth.cli
|
||||
from cic_eth.server.app import create_app
|
||||
from cic_eth.server.celery import create_celery_wrapper
|
||||
|
||||
arg_flags = cic_eth.cli.argflag_std_base
|
||||
local_arg_flags = cic_eth.cli.argflag_local_taskcallback
|
||||
argparser = cic_eth.cli.ArgumentParser(arg_flags)
|
||||
argparser.process_local_flags(local_arg_flags)
|
||||
args = argparser.parse_args()
|
||||
config = cic_eth.cli.Config.from_args(args, arg_flags, local_arg_flags)
|
||||
# Define log levels
|
||||
if args.vv:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
elif args.v:
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
|
||||
# Setup Celery App
|
||||
celery_app = cic_eth.cli.CeleryApp.from_config(config)
|
||||
celery_app.set_default()
|
||||
|
||||
|
||||
chain_spec = config.get('CHAIN_SPEC')
|
||||
celery_queue = config.get('CELERY_QUEUE')
|
||||
redis_host = config.get('REDIS_HOST')
|
||||
redis_port = config.get('REDIS_PORT')
|
||||
redis_db = config.get('REDIS_DB')
|
||||
redis_timeout = config.get('REDIS_TIMEOUT')
|
||||
|
||||
celery_wrapper = create_celery_wrapper(celery_queue=celery_queue, chain_spec=chain_spec,
|
||||
redis_db=redis_db, redis_host=redis_host, redis_port=redis_port, redis_timeout=redis_timeout)
|
||||
app = create_app(celery_wrapper)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=5000, log_level="info")
|
||||
@@ -1,3 +0,0 @@
|
||||
from . import converters
|
||||
from . import cache
|
||||
from . import celery
|
||||
@@ -1,119 +0,0 @@
|
||||
import logging
|
||||
import sys
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from cic_eth.server import cache, converters
|
||||
from cic_eth.server.cache import setup_cache
|
||||
from cic_eth.server.celery import create_celery_wrapper
|
||||
from cic_eth.server.models import (DefaultToken, Token, TokenBalance,
|
||||
Transaction)
|
||||
from fastapi import FastAPI, Query
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_app(celery_wrapper):
|
||||
|
||||
app = FastAPI(debug=True,
|
||||
title="Grassroots Economics",
|
||||
description="CIC ETH API",
|
||||
version="0.0.1",
|
||||
terms_of_service="https://www.grassrootseconomics.org/pages/terms-and-conditions.html",
|
||||
contact={
|
||||
"name": "Grassroots Economics",
|
||||
"url": "https://www.grassrootseconomics.org",
|
||||
"email": "will@grassecon.org"
|
||||
},
|
||||
license_info={
|
||||
"name": "GPLv3",
|
||||
})
|
||||
|
||||
@app.get("/transactions", response_model=List[Transaction])
|
||||
def transactions(address: str, limit: Optional[str] = 10):
|
||||
return celery_wrapper('list', address, limit=limit)
|
||||
|
||||
@app.get("/balance", response_model=List[TokenBalance])
|
||||
def balance(token_symbol: str, address: str = Query(..., title="Address", min_length=40, max_length=42), include_pending: bool = True):
|
||||
log.info(f"address: {address}")
|
||||
log.info(f"token_symbol: {token_symbol}")
|
||||
data = celery_wrapper('balance', address, token_symbol,
|
||||
include_pending=include_pending)
|
||||
for b in data:
|
||||
token = get_token(token_symbol)
|
||||
b['balance_network'] = converters.from_wei(
|
||||
token.decimals, int(b['balance_network']))
|
||||
b['balance_incoming'] = converters.from_wei(
|
||||
token.decimals, int(b['balance_incoming']))
|
||||
b['balance_outgoing'] = converters.from_wei(
|
||||
token.decimals, int(b['balance_outgoing']))
|
||||
|
||||
b.update({
|
||||
"balance_available": int(b['balance_network']) + int(b['balance_incoming']) - int(b['balance_outgoing'])
|
||||
})
|
||||
return data
|
||||
|
||||
@app.post("/create_account")
|
||||
def create_account(password: Optional[str] = None, register: bool = True):
|
||||
data = celery_wrapper(
|
||||
'create_account', password=password, register=register)
|
||||
return data
|
||||
|
||||
# def refill_gas(start_response, query: dict):
|
||||
# address = query.pop('address')
|
||||
# data = celery_wrapper('refill_gas', address)
|
||||
# return data
|
||||
|
||||
# def ping(start_response, query: dict):
|
||||
# data = celery_wrapper('ping', **query)
|
||||
# return data
|
||||
|
||||
@app.post("/transfer")
|
||||
def transfer(from_address: str, to_address: str, value: int, token_symbol: str):
|
||||
token = get_token(
|
||||
token_symbol)
|
||||
wei_value = converters.to_wei(token.decimals, int(value))
|
||||
data = celery_wrapper('transfer', from_address,
|
||||
to_address, wei_value, token_symbol)
|
||||
return data
|
||||
|
||||
@app.post("/transfer_from")
|
||||
def transfer_from(from_address: str, to_address: str, value: int, token_symbol: str, spender_address: str):
|
||||
token = get_token(
|
||||
token_symbol)
|
||||
wei_value = converters.to_wei(token.decimals, int(value))
|
||||
data = celery_wrapper('transfer_from', from_address, to_address,
|
||||
wei_value, token_symbol, spender_address)
|
||||
return data
|
||||
|
||||
@app.get("/token", response_model=Token)
|
||||
def token(token_symbol: str, proof: Optional[str] = None):
|
||||
token = get_token(token_symbol)
|
||||
if token == None:
|
||||
sys.stderr.write(f"Cached Token {token_symbol} not found")
|
||||
data = celery_wrapper('token', token_symbol, proof=proof)
|
||||
token = Token.new(data)
|
||||
sys.stderr.write(f"Token {token}")
|
||||
|
||||
return token
|
||||
|
||||
@app.get("/tokens", response_model=List[Token])
|
||||
def tokens(token_symbols: Optional[List[str]] = Query(None), proof: Optional[Union[str, List[str], List[List[str]]]] = None):
|
||||
data = celery_wrapper('tokens', token_symbols,
|
||||
catch=len(token_symbols), proof=proof)
|
||||
if data:
|
||||
tokens = []
|
||||
for token in data:
|
||||
print(f"Token: {token}")
|
||||
tokens.append(Token.new(token))
|
||||
return tokens
|
||||
return None
|
||||
|
||||
@app.get("/default_token", response_model=DefaultToken)
|
||||
def default_token():
|
||||
data = celery_wrapper('default_token')
|
||||
return data
|
||||
|
||||
def get_token(token_symbol: str):
|
||||
data = celery_wrapper('token', token_symbol)
|
||||
return Token.new(data)
|
||||
return app
|
||||
@@ -1,130 +0,0 @@
|
||||
# standard imports
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
from typing import Optional, Union
|
||||
|
||||
from cic_eth.server.models import Token
|
||||
from cic_types.condiments import MetadataPointer
|
||||
from redis import Redis, StrictRedis
|
||||
|
||||
logg = logging.getLogger(__file__)
|
||||
|
||||
|
||||
class Cache:
|
||||
store: Redis = None
|
||||
|
||||
|
||||
def setup_cache(redis_host, redis_port, redis_db):
|
||||
# Define universal redis cache access
|
||||
Cache.store = StrictRedis(
|
||||
host=redis_host, port=redis_port, db=redis_db, decode_responses=True)
|
||||
|
||||
|
||||
def get_token_data(token_symbol: str):
|
||||
"""
|
||||
:param token_symbol:
|
||||
:type token_symbol:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
identifier = [token_symbol.encode('utf-8')]
|
||||
key = cache_data_key(identifier, MetadataPointer.TOKEN_DATA)
|
||||
logg.debug(f'Retrieving token data for: {token_symbol} at: {key}')
|
||||
token_data_str = get_cached_data(key=key)
|
||||
if(token_data_str is None):
|
||||
logg.debug(f'No token data found for: {token_symbol}')
|
||||
return None
|
||||
else:
|
||||
token_data = json.loads(token_data_str)
|
||||
logg.debug(f'Retrieved token data: {token_data}')
|
||||
return token_data
|
||||
|
||||
|
||||
def set_token_data(token_symbol: str, token: dict):
|
||||
"""
|
||||
:param token_symbol:
|
||||
:type token_symbol:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
identifier = [token_symbol.encode('utf-8')]
|
||||
key = cache_data_key(identifier, MetadataPointer.TOKEN_DATA)
|
||||
cache_data(key, json.dumps(token))
|
||||
logg.debug(f'Cached token data for: {token_symbol} at: {key}')
|
||||
|
||||
|
||||
def get_default_token() -> Optional[str]:
|
||||
"""This function attempts to retrieve the default token's data from the redis cache.
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
logg.debug(f'Retrieving default token from cache')
|
||||
# TODO: What should the identifier be?
|
||||
key = cache_data_key(identifier="ff".encode('utf-8'),
|
||||
salt=MetadataPointer.TOKEN_DEFAULT)
|
||||
default_token_str = get_cached_data(key=key)
|
||||
if default_token_str is None:
|
||||
logg.debug(f'No cached default token found: {key}')
|
||||
return None
|
||||
default_token = json.loads(default_token_str)
|
||||
logg.debug(f'Retrieved default token data: {default_token}')
|
||||
return default_token
|
||||
|
||||
|
||||
def set_default_token(default_token: dict):
|
||||
"""
|
||||
:param default_token:
|
||||
:type default_token:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
logg.debug(f'Setting default token in cache')
|
||||
key = cache_data_key(identifier="ff".encode('utf-8'),
|
||||
salt=MetadataPointer.TOKEN_DEFAULT)
|
||||
cache_data(key, json.dumps(default_token))
|
||||
|
||||
|
||||
def cache_data(key: str, data: str):
|
||||
"""
|
||||
:param key:
|
||||
:type key:
|
||||
:param data:
|
||||
:type data:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
cache = Cache.store
|
||||
cache.set(name=key, value=data)
|
||||
cache.persist(name=key)
|
||||
logg.debug(f'caching: {data} with key: {key}.')
|
||||
|
||||
|
||||
def get_cached_data(key: str):
|
||||
"""
|
||||
:param key:
|
||||
:type key:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
cache = Cache.store
|
||||
return cache.get(name=key)
|
||||
|
||||
|
||||
def cache_data_key(identifier: Union[list, bytes], salt: MetadataPointer):
|
||||
"""
|
||||
:param identifier:
|
||||
:type identifier:
|
||||
:param salt:
|
||||
:type salt:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
hash_object = hashlib.new("sha256")
|
||||
if isinstance(identifier, list):
|
||||
for identity in identifier:
|
||||
hash_object.update(identity)
|
||||
else:
|
||||
hash_object.update(identifier)
|
||||
hash_object.update(salt.value.encode(encoding="utf-8"))
|
||||
return hash_object.digest().hex()
|
||||
@@ -1,64 +0,0 @@
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
import redis
|
||||
from cic_eth.api.api_task import Api
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_celery_wrapper(chain_spec,
|
||||
celery_queue,
|
||||
redis_host,
|
||||
redis_port,
|
||||
redis_db,
|
||||
redis_timeout):
|
||||
def call(method, *args, catch=1, **kwargs):
|
||||
""" Creates a redis channel and calls `cic_eth.api` with the provided `method` and `*args`. Returns the result of the api call. Catch allows you to specify how many messages to catch before returning.
|
||||
"""
|
||||
log.debug(f"Using redis: {redis_host}, {redis_port}, {redis_db}")
|
||||
redis_channel = str(uuid.uuid4())
|
||||
r = redis.Redis(redis_host, redis_port, redis_db)
|
||||
ps = r.pubsub()
|
||||
ps.subscribe(redis_channel)
|
||||
api = Api(
|
||||
chain_spec,
|
||||
queue=celery_queue,
|
||||
callback_param='{}:{}:{}:{}'.format(
|
||||
redis_host, redis_port, redis_db, redis_channel),
|
||||
callback_task='cic_eth.callbacks.redis.redis',
|
||||
callback_queue=celery_queue,
|
||||
)
|
||||
getattr(api, method)(*args, **kwargs)
|
||||
|
||||
ps.get_message()
|
||||
try:
|
||||
data = []
|
||||
if catch == 1:
|
||||
message = ps.get_message(timeout=redis_timeout)
|
||||
data = json.loads(message['data'])["result"]
|
||||
raise data
|
||||
else:
|
||||
for _i in range(catch):
|
||||
message = ps.get_message(
|
||||
timeout=redis_timeout)
|
||||
result = json.loads(message['data'])["result"]
|
||||
data.append(result)
|
||||
|
||||
except TimeoutError as e:
|
||||
sys.stderr.write(
|
||||
f"cic_eth.api.{method}({args}, {kwargs}) timed out:\n {e}")
|
||||
raise e
|
||||
except Exception as e:
|
||||
sys.stderr.write(
|
||||
f'Unable to parse Data:\n{data}\n Error:\n{e}')
|
||||
raise e
|
||||
|
||||
log.debug(
|
||||
f"cic_eth.api.{method}(args={args}, kwargs={kwargs})\n {data}")
|
||||
|
||||
ps.unsubscribe()
|
||||
return data
|
||||
return call
|
||||
@@ -1,39 +0,0 @@
|
||||
# Stolen from ussd
|
||||
|
||||
from math import trunc
|
||||
|
||||
def from_wei(decimals: int, value: int) -> float:
|
||||
"""This function converts values in Wei to a token in the cic network.
|
||||
:param decimals: The decimals required for wei values.
|
||||
:type decimals: int
|
||||
:param value: Value in Wei
|
||||
:type value: int
|
||||
:return: SRF equivalent of value in Wei
|
||||
:rtype: float
|
||||
"""
|
||||
value = float(value) / (10**decimals)
|
||||
return truncate(value=value, decimals=2)
|
||||
|
||||
|
||||
def to_wei(decimals: int, value: int) -> int:
|
||||
"""This functions converts values from a token in the cic network to Wei.
|
||||
:param decimals: The decimals required for wei values.
|
||||
:type decimals: int
|
||||
:param value: Value in SRF
|
||||
:type value: int
|
||||
:return: Wei equivalent of value in SRF
|
||||
:rtype: int
|
||||
"""
|
||||
return int(value * (10**decimals))
|
||||
|
||||
def truncate(value: float, decimals: int) -> float:
|
||||
"""This function truncates a value to a specified number of decimals places.
|
||||
:param value: The value to be truncated.
|
||||
:type value: float
|
||||
:param decimals: The number of decimals for the value to be truncated to
|
||||
:type decimals: int
|
||||
:return: The truncated value.
|
||||
:rtype: int
|
||||
"""
|
||||
stepper = 10.0**decimals
|
||||
return trunc(stepper*value) / stepper
|
||||
@@ -1,92 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class Transaction(BaseModel):
|
||||
block_number: Optional[int] = Field(None, example=24531)
|
||||
date_checked: Optional[str] = Field(
|
||||
None, example='2021-11-12T09:36:40.725296')
|
||||
date_created: Optional[str] = Field(
|
||||
None, example='2021-11-12T09:36:40.131292')
|
||||
date_updated: Optional[str] = Field(
|
||||
None, example='2021-11-12T09:36:40.131292')
|
||||
destination_token: Optional[str] = Field(
|
||||
None, example=365185044137427460620354810422988491181438940190
|
||||
)
|
||||
destination_token_decimals: Optional[int] = Field(None, example=6)
|
||||
destination_token_symbol: Optional[str] = Field(None, example='COFE')
|
||||
from_value: Optional[int] = Field(None, example=100000000)
|
||||
hash: Optional[str] = Field(
|
||||
None,
|
||||
example=90380195350511178677041624165156640995490505896556680958001954705731707874291,
|
||||
)
|
||||
nonce: Optional[int] = Field(None, example=1)
|
||||
recipient: Optional[str] = Field(
|
||||
None, example='872e1ec9d499b242ebfcfd0a279a4c3e0cd472c0'
|
||||
)
|
||||
sender: Optional[str] = Field(
|
||||
None, example='1a92b05e0b880127a4c26ac0f68a52df3ac6b89d'
|
||||
)
|
||||
signed_tx: Optional[str] = Field(
|
||||
None,
|
||||
example=1601943273486236942256143665779318355236220334071247753507187634376562549990085710958441113013370129915441072693447256942510246386178938683325073160349857879326297351587330623503997011254644396580777843154770873208185332563272343361515226115860084201932230246018679661802320007832375955345977725551120479084062615799940692628221555193198194825737613358738414884130187144700126061702642574663703095161159219410608270,
|
||||
)
|
||||
source_token: Optional[str] = Field(
|
||||
None, example=365185044137427460620354810422988491181438940190
|
||||
)
|
||||
source_token_decimals: Optional[int] = Field(None, example=6)
|
||||
source_token_symbol: Optional[str] = Field(None, example='COFE')
|
||||
status: Optional[str] = Field(None, example='SUCCESS')
|
||||
status_code: Optional[int] = Field(None, example=4104)
|
||||
timestamp: Optional[int] = Field(None, example=1636709800)
|
||||
to_value: Optional[int] = Field(None, example=100000000)
|
||||
tx_hash: Optional[str] = Field(
|
||||
None,
|
||||
example=90380195350511178677041624165156640995490505896556680958001954705731707874291,
|
||||
)
|
||||
tx_index: Optional[int] = Field(None, example=0)
|
||||
|
||||
|
||||
class DefaultToken(BaseModel):
|
||||
symbol: Optional[str] = Field(None, description='Token Symbol')
|
||||
address: Optional[str] = Field(None, description='Token Address')
|
||||
name: Optional[str] = Field(None, description='Token Name')
|
||||
decimals: Optional[int] = Field(None, description='Decimals')
|
||||
|
||||
|
||||
class TokenBalance(BaseModel):
|
||||
address: Optional[str] = None
|
||||
converters: Optional[List[str]] = None
|
||||
balance_network: Optional[int] = None
|
||||
balance_incoming: Optional[int] = None
|
||||
balance_outgoing: Optional[int] = None
|
||||
balance_available: Optional[int] = None
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
decimals: Optional[int] = None
|
||||
name: Optional[str] = None
|
||||
address: Optional[str] = None
|
||||
symbol: Optional[str] = None
|
||||
proofs: Optional[List[str]] = None
|
||||
converters: Optional[List[str]] = None
|
||||
proofs_with_signers: Optional[List[Proof]] = None
|
||||
|
||||
@staticmethod
|
||||
def new(data: List[dict]) -> Token:
|
||||
proofs_with_signers = [{"proof": proof, "signers": signers}
|
||||
for (proof, signers) in data[1].items()]
|
||||
return Token(**data[0],
|
||||
proofs_with_signers=proofs_with_signers,
|
||||
)
|
||||
|
||||
|
||||
class Proof(BaseModel):
|
||||
proof: Optional[str] = None
|
||||
signers: Optional[List[str]] = None
|
||||
|
||||
|
||||
Token.update_forward_refs()
|
||||
@@ -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, MaxGasOracle
|
||||
from cic_eth.eth.util import CacheGasOracle
|
||||
|
||||
#logg = logging.getLogger().getChild(__name__)
|
||||
logg = logging.getLogger()
|
||||
@@ -41,24 +41,21 @@ class BaseTask(celery.Task):
|
||||
|
||||
|
||||
def create_gas_oracle(self, conn, address=None, *args, **kwargs):
|
||||
x = None
|
||||
if address is None:
|
||||
x = RPCGasOracle(
|
||||
if address == None:
|
||||
return 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:
|
||||
|
||||
x = MaxGasOracle(conn)
|
||||
x.code_callback = x.get_fee_units
|
||||
|
||||
return x
|
||||
|
||||
|
||||
def get_min_fee_limit(self, code):
|
||||
return self.min_fee_limit
|
||||
return CacheGasOracle(
|
||||
conn,
|
||||
address,
|
||||
method=kwargs.get('method'),
|
||||
min_price=self.min_fee_price,
|
||||
id_generator=kwargs.get('id_generator'),
|
||||
)
|
||||
|
||||
|
||||
def get_min_fee_limit(self, code):
|
||||
@@ -87,7 +84,7 @@ class BaseTask(celery.Task):
|
||||
)
|
||||
s.apply_async()
|
||||
|
||||
|
||||
|
||||
class CriticalTask(BaseTask):
|
||||
retry_jitter = True
|
||||
retry_backoff = True
|
||||
@@ -99,7 +96,7 @@ class CriticalSQLAlchemyTask(CriticalTask):
|
||||
sqlalchemy.exc.DatabaseError,
|
||||
sqlalchemy.exc.TimeoutError,
|
||||
sqlalchemy.exc.ResourceClosedError,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class CriticalWeb3Task(CriticalTask):
|
||||
@@ -107,7 +104,7 @@ class CriticalWeb3Task(CriticalTask):
|
||||
ConnectionError,
|
||||
)
|
||||
safe_gas_threshold_amount = 60000 * 3
|
||||
safe_gas_refill_amount = safe_gas_threshold_amount * 5
|
||||
safe_gas_refill_amount = safe_gas_threshold_amount * 5
|
||||
safe_gas_gifter_balance = safe_gas_threshold_amount * 5 * 100
|
||||
|
||||
|
||||
@@ -125,13 +122,13 @@ class CriticalSQLAlchemyAndSignerTask(CriticalTask):
|
||||
sqlalchemy.exc.DatabaseError,
|
||||
sqlalchemy.exc.TimeoutError,
|
||||
sqlalchemy.exc.ResourceClosedError,
|
||||
)
|
||||
)
|
||||
|
||||
class CriticalWeb3AndSignerTask(CriticalWeb3Task):
|
||||
autoretry_for = (
|
||||
ConnectionError,
|
||||
)
|
||||
|
||||
|
||||
@celery_app.task()
|
||||
def check_health(self):
|
||||
pass
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
[redis]
|
||||
host=redis
|
||||
database=0
|
||||
password=
|
||||
port=6379
|
||||
@@ -5,5 +5,3 @@ urlybird~=0.0.1
|
||||
cic-eth-registry~=0.6.6
|
||||
cic-types~=0.2.1a8
|
||||
cic-eth-aux-erc20-demurrage-token~=0.0.3
|
||||
fastapi[all]==0.70.1
|
||||
uvicorn[standard]<0.16.0
|
||||
@@ -36,7 +36,6 @@ packages =
|
||||
cic_eth.db.models
|
||||
cic_eth.queue
|
||||
cic_eth.ext
|
||||
cic_eth.server
|
||||
cic_eth.runnable
|
||||
cic_eth.runnable.daemons
|
||||
cic_eth.runnable.daemons.filters
|
||||
|
||||
@@ -24,8 +24,6 @@ from cic_eth.pytest.fixtures_database import *
|
||||
from cic_eth.pytest.fixtures_role import *
|
||||
from cic_eth.pytest.fixtures_contract import *
|
||||
from cic_eth.pytest.fixtures_token import *
|
||||
from cic_eth.pytest.patches.account import *
|
||||
|
||||
from chainlib.eth.pytest import *
|
||||
from eth_contract_registry.pytest import *
|
||||
from cic_eth_registry.pytest.fixtures_contracts import *
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
set -e
|
||||
|
||||
pip install --extra-index-url https://pip.grassrootseconomics.net --extra-index-url https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple \
|
||||
-r admin_requirements.txt \
|
||||
-r services_requirements.txt \
|
||||
pip install --extra-index-url https://pip.grassrootseconomics.net --extra-index-url https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple
|
||||
-r admin_requirements.txt
|
||||
-r services_requirements.txt
|
||||
-r test_requirements.txt
|
||||
|
||||
export PYTHONPATH=. && pytest -x --cov=cic_eth --cov-fail-under=90 --cov-report term-missing tests
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
# coding: utf-8
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
import hexathon
|
||||
import pytest
|
||||
from cic_eth.server.app import create_app
|
||||
from cic_eth.server.celery import create_celery_wrapper
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def celery_wrapper(api):
|
||||
""" Creates a redis channel and calls `cic_eth.api` with the provided `method` and `*args`. Returns the result of the api call. Catch allows you to specify how many messages to catch before returning.
|
||||
"""
|
||||
def wrapper(method, *args, catch=1, **kwargs):
|
||||
t = getattr(api, method)(*args, **kwargs)
|
||||
return t.get()
|
||||
return wrapper
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def client(celery_wrapper):
|
||||
app = create_app(celery_wrapper)
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
def test_default_token(client,mock_sync_default_token_api_query):
|
||||
# Default Token
|
||||
response = client.get('/default_token')
|
||||
log.debug(f"balance response {response}")
|
||||
default_token = response.json()
|
||||
assert default_token == {'symbol': 'FOO', 'address': '0xe7c559c40B297d7f039767A2c3677E20B24F1385', 'name': 'Giftable Token', 'decimals': 6}
|
||||
|
||||
def test_token(client, mock_token_api_query):
|
||||
# Default Token
|
||||
response = client.get('/token?token_symbol=FOO')
|
||||
log.debug(f"token response {response}")
|
||||
token = response.json()
|
||||
assert token == {
|
||||
'address': '0xe7c559c40B297d7f039767A2c3677E20B24F1385',
|
||||
'converters': [],
|
||||
'decimals': 6,
|
||||
'name': 'Giftable Token',
|
||||
'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'],
|
||||
'proofs_with_signers': [{'proof': '5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3',
|
||||
'signers': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C', 'Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']}],
|
||||
'symbol': 'FOO',
|
||||
}
|
||||
|
||||
def test_tokens(client, mock_tokens_api_query):
|
||||
# Default Token
|
||||
response = client.get(
|
||||
'/tokens', params={'token_symbols': ['FOO', 'FOO']})
|
||||
|
||||
log.debug(f"tokens response {response}")
|
||||
tokens = response.json()
|
||||
assert tokens == [
|
||||
{
|
||||
'address': '0xe7c559c40B297d7f039767A2c3677E20B24F1385',
|
||||
'converters': [],
|
||||
'decimals': 6,
|
||||
'name': 'Giftable Token',
|
||||
'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'],
|
||||
'proofs_with_signers': [{'proof': '5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3',
|
||||
'signers': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C', 'Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']}],
|
||||
'symbol': 'FOO',
|
||||
},
|
||||
{
|
||||
'address': '0xe7c559c40B297d7f039767A2c3677E20B24F1385',
|
||||
'converters': [],
|
||||
'decimals': 6,
|
||||
'name': 'Giftable Token',
|
||||
'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'],
|
||||
'proofs_with_signers': [{'proof': '5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3',
|
||||
'signers': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C', 'Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']}],
|
||||
'symbol': 'FOO',
|
||||
},
|
||||
]
|
||||
|
||||
@pytest.mark.skip("Not implemented")
|
||||
def test_account(client):
|
||||
# Default Token
|
||||
response = client.get('/default_token')
|
||||
log.debug(f"balance response {response}")
|
||||
default_token = response.json()
|
||||
|
||||
# Create Account 1
|
||||
params = {
|
||||
'password': '',
|
||||
'register': True
|
||||
}
|
||||
response = client.post(
|
||||
'/create_account',
|
||||
params=params)
|
||||
address_1 = hexathon.valid(response.json())
|
||||
|
||||
# Create Account 2
|
||||
params = {
|
||||
'password': '',
|
||||
'register': True
|
||||
}
|
||||
response = client.post('/create_account',
|
||||
params=params)
|
||||
address_2 = hexathon.valid(response.json())
|
||||
time.sleep(30) # Required to allow balance to show
|
||||
|
||||
# Balance Account 1
|
||||
params = {
|
||||
'address': address_1,
|
||||
'token_symbol': 'COFE',
|
||||
'include_pending': True
|
||||
}
|
||||
response = client.get('/balance',
|
||||
params=params)
|
||||
balance = response.json()
|
||||
|
||||
assert (balance[0] == {
|
||||
"address": default_token.get('address').lower(),
|
||||
"balance_available": 30000,
|
||||
"balance_incoming": 0,
|
||||
"balance_network": 30000,
|
||||
"balance_outgoing": 0,
|
||||
"converters": []
|
||||
})
|
||||
|
||||
# Transfer
|
||||
params = {
|
||||
'from_address': address_1,
|
||||
'to_address': address_2,
|
||||
'value': 100,
|
||||
'token_symbol': 'COFE'
|
||||
}
|
||||
response = client.post('/transfer',
|
||||
params=params)
|
||||
transfer = response.json()
|
||||
|
||||
# Balance Account 1
|
||||
params = {
|
||||
'address': address_1,
|
||||
'token_symbol': 'COFE',
|
||||
'include_pending': True
|
||||
}
|
||||
response = client.get('/balance',
|
||||
params=params)
|
||||
balance_after_transfer = response.json()
|
||||
assert (balance_after_transfer[0] == {
|
||||
"address": default_token.get('address').lower(),
|
||||
"balance_available": 29900,
|
||||
"balance_incoming": 0,
|
||||
"balance_network": 30000,
|
||||
"balance_outgoing": 100,
|
||||
"converters": []
|
||||
})
|
||||
|
||||
# Transactions Account 1
|
||||
params = {
|
||||
'address': address_1,
|
||||
'limit': 10
|
||||
}
|
||||
response = client.get('/transactions',
|
||||
params=params)
|
||||
transactions = response.json()
|
||||
# TODO: What are the other 2 transactions
|
||||
assert len(transactions) == 3
|
||||
# Check the transaction is correct
|
||||
# TODO wtf is READSEND (Ready to send? Or already sent)
|
||||
assert transactions[0].status == 'READYSEND'
|
||||
@@ -1,9 +1,10 @@
|
||||
[database]
|
||||
name=cic_notify_test
|
||||
user=
|
||||
password=
|
||||
host=localhost
|
||||
port=
|
||||
engine=sqlite
|
||||
driver=pysqlite
|
||||
debug=0
|
||||
[DATABASE]
|
||||
user = postgres
|
||||
password =
|
||||
host = localhost
|
||||
port = 5432
|
||||
name = /tmp/cic-notify.db
|
||||
#engine = postgresql
|
||||
#driver = psycopg2
|
||||
engine = sqlite
|
||||
driver = pysqlite
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
[report]
|
||||
omit =
|
||||
venv/*
|
||||
scripts/*
|
||||
cic_notify/db/migrations/*
|
||||
cic_notify/runnable/*
|
||||
cic_notify/version.py
|
||||
@@ -3,7 +3,6 @@ import logging
|
||||
import re
|
||||
|
||||
# third-party imports
|
||||
import cic_notify.tasks.sms.db
|
||||
from celery.app.control import Inspect
|
||||
import celery
|
||||
|
||||
@@ -14,16 +13,45 @@ 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:
|
||||
def __init__(self, queue: any = 'cic-notify'):
|
||||
# TODO: Implement callback strategy
|
||||
def __init__(self, queue=None):
|
||||
"""
|
||||
: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: str, recipient: str):
|
||||
|
||||
def sms(self, message, recipient):
|
||||
"""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
|
||||
@@ -32,9 +60,24 @@ class Api:
|
||||
:return: a celery Task
|
||||
:rtype: Celery.Task
|
||||
"""
|
||||
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)()
|
||||
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
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
script_location = .
|
||||
script_location = migrations
|
||||
|
||||
# template used to generate migration files
|
||||
# file_template = %%(rev)s_%%(slug)s
|
||||
@@ -27,17 +27,28 @@ script_location = .
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; this defaults
|
||||
# to ./versions. When using multiple version
|
||||
# to migrations/versions. When using multiple version
|
||||
# directories, initial revisions must be specified with --version-path
|
||||
# version_locations = %(here)s/bar %(here)s/bat ./versions
|
||||
# version_locations = %(here)s/bar %(here)s/bat migrations/versions
|
||||
|
||||
# the output encoding used when revision files
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||
sqlalchemy.url = postgres+psycopg2://postgres@localhost/cic-notify
|
||||
|
||||
|
||||
[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, disable_existing_loggers=True)
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
@@ -56,14 +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(
|
||||
context.config.get_section(context.config.config_ini_section),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
connectable = engine_from_config(
|
||||
config.get_section(config.config_ini_section),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(
|
||||
|
||||
@@ -7,7 +7,7 @@ import celery
|
||||
|
||||
celery_app = celery.current_app
|
||||
logg = celery_app.log.get_default_logger()
|
||||
local_logg = logging.getLogger()
|
||||
local_logg = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@celery_app.task
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
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
|
||||
pytest~=6.0.1
|
||||
pytest-celery~=0.0.0a1
|
||||
pytest-mock~=3.3.1
|
||||
pysqlite3~=0.4.3
|
||||
pytest-cov==2.10.1
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
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.')
|
||||
@@ -1,27 +0,0 @@
|
||||
# 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
|
||||
@@ -1,38 +0,0 @@
|
||||
# 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")}'
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
# 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}'
|
||||
@@ -1,26 +0,0 @@
|
||||
# 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
|
||||
@@ -1,21 +0,0 @@
|
||||
# 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
|
||||
@@ -1,24 +0,0 @@
|
||||
# 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,13 +1,31 @@
|
||||
# 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
|
||||
|
||||
# test imports
|
||||
# fixtures
|
||||
from tests.fixtures_config import *
|
||||
from tests.fixtures_celery import *
|
||||
from tests.fixtures_database import *
|
||||
|
||||
from .fixtures.celery import *
|
||||
from .fixtures.config import *
|
||||
from .fixtures.database import *
|
||||
from .fixtures.result import *
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
#@pytest.fixture(scope='session')
|
||||
#def africastalking_notification(
|
||||
# load_config,
|
||||
# ):
|
||||
# return AfricastalkingNotificationTransport(load_config)
|
||||
#
|
||||
|
||||
32
apps/cic-notify/tests/fixtures/config.py
vendored
32
apps/cic-notify/tests/fixtures/config.py
vendored
@@ -1,32 +0,0 @@
|
||||
# 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
54
apps/cic-notify/tests/fixtures/database.py
vendored
@@ -1,54 +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 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
24
apps/cic-notify/tests/fixtures/result.py
vendored
@@ -1,24 +0,0 @@
|
||||
# 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"
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,12 @@ 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
|
||||
20
apps/cic-notify/tests/fixtures_config.py
Normal file
20
apps/cic-notify/tests/fixtures_config.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# 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
|
||||
48
apps/cic-notify/tests/fixtures_database.py
Normal file
48
apps/cic-notify/tests/fixtures_database.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# 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()
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# 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')
|
||||
34
apps/cic-notify/tests/test_sms.py
Normal file
34
apps/cic-notify/tests/test_sms.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# 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()
|
||||
@@ -3,5 +3,4 @@ omit =
|
||||
venv/*
|
||||
scripts/*
|
||||
cic_ussd/db/migrations/*
|
||||
cic_ussd/runnable/*
|
||||
cic_ussd/version.py
|
||||
cic_ussd/runnable/*
|
||||
@@ -1,22 +0,0 @@
|
||||
# standard imports
|
||||
|
||||
# external imports
|
||||
|
||||
# local imports
|
||||
|
||||
class Guardianship:
|
||||
guardians: list = []
|
||||
|
||||
@classmethod
|
||||
def load_system_guardians(cls, guardians_file: str):
|
||||
with open(guardians_file, 'r') as system_guardians:
|
||||
cls.guardians = [line.strip() for line in system_guardians]
|
||||
|
||||
def is_system_guardian(self, phone_number: str):
|
||||
"""
|
||||
:param phone_number:
|
||||
:type phone_number:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
return phone_number in self.guardians
|
||||
@@ -13,6 +13,7 @@ from cic_types.condiments import MetadataPointer
|
||||
from cic_ussd.account.chain import Chain
|
||||
from cic_ussd.account.transaction import from_wei
|
||||
from cic_ussd.cache import cache_data_key, get_cached_data
|
||||
from cic_ussd.translation import translation_for
|
||||
|
||||
logg = logging.getLogger(__name__)
|
||||
|
||||
@@ -96,3 +97,17 @@ def query_statement(blockchain_address: str, limit: int = 9):
|
||||
callback_param=blockchain_address
|
||||
)
|
||||
cic_eth_api.list(address=blockchain_address, limit=limit)
|
||||
|
||||
|
||||
def statement_transaction_set(preferred_language: str, transaction_reprs: list):
|
||||
"""
|
||||
:param preferred_language:
|
||||
:type preferred_language:
|
||||
:param transaction_reprs:
|
||||
:type transaction_reprs:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
if not transaction_reprs:
|
||||
return translation_for('helpers.no_transaction_history', preferred_language)
|
||||
return ''.join(f'{transaction_repr}\n' for transaction_repr in transaction_reprs)
|
||||
|
||||
@@ -15,6 +15,7 @@ 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.translation import translation_for
|
||||
|
||||
logg = logging.getLogger(__file__)
|
||||
|
||||
@@ -325,3 +326,16 @@ def set_active_token(blockchain_address: str, token_symbol: str):
|
||||
cache_data(key=key, data=token_symbol)
|
||||
|
||||
|
||||
def token_list_set(preferred_language: str, token_data_reprs: list):
|
||||
"""
|
||||
:param preferred_language:
|
||||
:type preferred_language:
|
||||
:param token_data_reprs:
|
||||
:type token_data_reprs:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
if not token_data_reprs:
|
||||
return translation_for('helpers.no_tokens_list', preferred_language)
|
||||
return ''.join(f'{token_data_repr}\n' for token_data_repr in token_data_reprs)
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class Cache:
|
||||
store: Redis = None
|
||||
|
||||
|
||||
def cache_data(key: str, data: [bytes, float, int, str]):
|
||||
def cache_data(key: str, data: str):
|
||||
"""
|
||||
:param key:
|
||||
:type key:
|
||||
@@ -55,6 +55,5 @@ def cache_data_key(identifier: Union[list, bytes], salt: MetadataPointer):
|
||||
hash_object.update(identity)
|
||||
else:
|
||||
hash_object.update(identifier)
|
||||
if salt != MetadataPointer.NONE:
|
||||
hash_object.update(salt.value.encode(encoding="utf-8"))
|
||||
hash_object.update(salt.value.encode(encoding="utf-8"))
|
||||
return hash_object.digest().hex()
|
||||
|
||||
@@ -63,7 +63,10 @@ class Account(SessionBase):
|
||||
def remove_guardian(self, phone_number: str):
|
||||
set_guardians = self.guardians.split(',')
|
||||
set_guardians.remove(phone_number)
|
||||
self.guardians = ','.join(set_guardians)
|
||||
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 []
|
||||
@@ -168,7 +171,7 @@ class Account(SessionBase):
|
||||
return check_password_hash(password, self.password_hash)
|
||||
|
||||
|
||||
def create(chain_str: str, phone_number: str, session: Session, preferred_language: str):
|
||||
def create(chain_str: str, phone_number: str, session: Session):
|
||||
"""
|
||||
:param chain_str:
|
||||
:type chain_str:
|
||||
@@ -176,14 +179,12 @@ def create(chain_str: str, phone_number: str, session: Session, preferred_langua
|
||||
:type phone_number:
|
||||
:param session:
|
||||
:type session:
|
||||
:param preferred_language:
|
||||
:type preferred_language:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
api = Api(callback_task='cic_ussd.tasks.callback_handler.account_creation_callback',
|
||||
callback_queue='cic-ussd',
|
||||
callback_param=preferred_language,
|
||||
callback_param='',
|
||||
chain_str=chain_str)
|
||||
task_uuid = api.create_account().id
|
||||
TaskTracker.add(session=session, task_uuid=task_uuid)
|
||||
|
||||
@@ -2,441 +2,417 @@
|
||||
"ussd_menu": {
|
||||
"1": {
|
||||
"description": "Entry point for users to select their preferred language.",
|
||||
"display_key": "ussd.initial_language_selection",
|
||||
"display_key": "ussd.kenya.initial_language_selection",
|
||||
"name": "initial_language_selection",
|
||||
"parent": null
|
||||
},
|
||||
"2": {
|
||||
"description": "Entry point for users to enter a pin to secure their account.",
|
||||
"display_key": "ussd.initial_pin_entry",
|
||||
"display_key": "ussd.kenya.initial_pin_entry",
|
||||
"name": "initial_pin_entry",
|
||||
"parent": null
|
||||
},
|
||||
"3": {
|
||||
"description": "Pin confirmation entry menu.",
|
||||
"display_key": "ussd.initial_pin_confirmation",
|
||||
"display_key": "ussd.kenya.initial_pin_confirmation",
|
||||
"name": "initial_pin_confirmation",
|
||||
"parent": "initial_pin_entry"
|
||||
},
|
||||
"4": {
|
||||
"description": "The signup process has been initiated and the account is being created.",
|
||||
"display_key": "ussd.account_creation_prompt",
|
||||
"display_key": "ussd.kenya.account_creation_prompt",
|
||||
"name": "account_creation_prompt",
|
||||
"parent": null
|
||||
},
|
||||
"5": {
|
||||
"description": "Entry point for activated users.",
|
||||
"display_key": "ussd.start",
|
||||
"display_key": "ussd.kenya.start",
|
||||
"name": "start",
|
||||
"parent": null
|
||||
},
|
||||
"6": {
|
||||
"description": "Given name entry menu.",
|
||||
"display_key": "ussd.enter_given_name",
|
||||
"display_key": "ussd.kenya.enter_given_name",
|
||||
"name": "enter_given_name",
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"7": {
|
||||
"description": "Family name entry menu.",
|
||||
"display_key": "ussd.enter_family_name",
|
||||
"display_key": "ussd.kenya.enter_family_name",
|
||||
"name": "enter_family_name",
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"8": {
|
||||
"description": "Gender entry menu.",
|
||||
"display_key": "ussd.enter_gender",
|
||||
"display_key": "ussd.kenya.enter_gender",
|
||||
"name": "enter_gender",
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"9": {
|
||||
"description": "Age entry menu.",
|
||||
"display_key": "ussd.enter_gender",
|
||||
"display_key": "ussd.kenya.enter_gender",
|
||||
"name": "enter_gender",
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"10": {
|
||||
"description": "Location entry menu.",
|
||||
"display_key": "ussd.enter_location",
|
||||
"display_key": "ussd.kenya.enter_location",
|
||||
"name": "enter_location",
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"11": {
|
||||
"description": "Products entry menu.",
|
||||
"display_key": "ussd.enter_products",
|
||||
"display_key": "ussd.kenya.enter_products",
|
||||
"name": "enter_products",
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"12": {
|
||||
"description": "Entry point for activated users.",
|
||||
"display_key": "ussd.start",
|
||||
"display_key": "ussd.kenya.start",
|
||||
"name": "start",
|
||||
"parent": null
|
||||
},
|
||||
"13": {
|
||||
"description": "Send Token recipient entry.",
|
||||
"display_key": "ussd.enter_transaction_recipient",
|
||||
"display_key": "ussd.kenya.enter_transaction_recipient",
|
||||
"name": "enter_transaction_recipient",
|
||||
"parent": "start"
|
||||
},
|
||||
"14": {
|
||||
"description": "Send Token amount prompt menu.",
|
||||
"display_key": "ussd.enter_transaction_amount",
|
||||
"display_key": "ussd.kenya.enter_transaction_amount",
|
||||
"name": "enter_transaction_amount",
|
||||
"parent": "start"
|
||||
},
|
||||
"15": {
|
||||
"description": "Pin entry for authorization to send token.",
|
||||
"display_key": "ussd.transaction_pin_authorization",
|
||||
"display_key": "ussd.kenya.transaction_pin_authorization",
|
||||
"name": "transaction_pin_authorization",
|
||||
"parent": "start"
|
||||
},
|
||||
"16": {
|
||||
"description": "Manage account menu.",
|
||||
"display_key": "ussd.account_management",
|
||||
"display_key": "ussd.kenya.account_management",
|
||||
"name": "account_management",
|
||||
"parent": "start"
|
||||
},
|
||||
"17": {
|
||||
"description": "Manage metadata menu.",
|
||||
"display_key": "ussd.metadata_management",
|
||||
"display_key": "ussd.kenya.metadata_management",
|
||||
"name": "metadata_management",
|
||||
"parent": "start"
|
||||
},
|
||||
"18": {
|
||||
"description": "Manage user's preferred language menu.",
|
||||
"display_key": "ussd.select_preferred_language",
|
||||
"display_key": "ussd.kenya.select_preferred_language",
|
||||
"name": "select_preferred_language",
|
||||
"parent": "account_management"
|
||||
},
|
||||
"19": {
|
||||
"description": "Retrieve mini-statement menu.",
|
||||
"display_key": "ussd.mini_statement_pin_authorization",
|
||||
"display_key": "ussd.kenya.mini_statement_pin_authorization",
|
||||
"name": "mini_statement_pin_authorization",
|
||||
"parent": "account_management"
|
||||
},
|
||||
"20": {
|
||||
"description": "Manage user's pin menu.",
|
||||
"display_key": "ussd.enter_current_pin",
|
||||
"display_key": "ussd.kenya.enter_current_pin",
|
||||
"name": "enter_current_pin",
|
||||
"parent": "account_management"
|
||||
},
|
||||
"21": {
|
||||
"description": "New pin entry menu.",
|
||||
"display_key": "ussd.enter_new_pin",
|
||||
"display_key": "ussd.kenya.enter_new_pin",
|
||||
"name": "enter_new_pin",
|
||||
"parent": "account_management"
|
||||
},
|
||||
"22": {
|
||||
"description": "Pin entry menu.",
|
||||
"display_key": "ussd.display_metadata_pin_authorization",
|
||||
"display_key": "ussd.kenya.display_metadata_pin_authorization",
|
||||
"name": "display_metadata_pin_authorization",
|
||||
"parent": "start"
|
||||
},
|
||||
"23": {
|
||||
"description": "Exit menu.",
|
||||
"display_key": "ussd.exit",
|
||||
"display_key": "ussd.kenya.exit",
|
||||
"name": "exit",
|
||||
"parent": null
|
||||
},
|
||||
"24": {
|
||||
"description": "Invalid menu option.",
|
||||
"display_key": "ussd.exit_invalid_menu_option",
|
||||
"display_key": "ussd.kenya.exit_invalid_menu_option",
|
||||
"name": "exit_invalid_menu_option",
|
||||
"parent": null
|
||||
},
|
||||
"25": {
|
||||
"description": "Pin policy violation.",
|
||||
"display_key": "ussd.exit_invalid_pin",
|
||||
"display_key": "ussd.kenya.exit_invalid_pin",
|
||||
"name": "exit_invalid_pin",
|
||||
"parent": null
|
||||
},
|
||||
"26": {
|
||||
"description": "Pin mismatch. New pin and the new pin confirmation do not match",
|
||||
"display_key": "ussd.exit_pin_mismatch",
|
||||
"display_key": "ussd.kenya.exit_pin_mismatch",
|
||||
"name": "exit_pin_mismatch",
|
||||
"parent": null
|
||||
},
|
||||
"27": {
|
||||
"description": "Ussd pin blocked Menu",
|
||||
"display_key": "ussd.exit_pin_blocked",
|
||||
"display_key": "ussd.kenya.exit_pin_blocked",
|
||||
"name": "exit_pin_blocked",
|
||||
"parent": null
|
||||
},
|
||||
"28": {
|
||||
"description": "Key params missing in request.",
|
||||
"display_key": "ussd.exit_invalid_request",
|
||||
"display_key": "ussd.kenya.exit_invalid_request",
|
||||
"name": "exit_invalid_request",
|
||||
"parent": null
|
||||
},
|
||||
"29": {
|
||||
"description": "The user did not select a choice.",
|
||||
"display_key": "ussd.exit_invalid_input",
|
||||
"display_key": "ussd.kenya.exit_invalid_input",
|
||||
"name": "exit_invalid_input",
|
||||
"parent": null
|
||||
},
|
||||
"30": {
|
||||
"description": "Exit following unsuccessful transaction due to insufficient account balance.",
|
||||
"display_key": "ussd.exit_insufficient_balance",
|
||||
"display_key": "ussd.kenya.exit_insufficient_balance",
|
||||
"name": "exit_insufficient_balance",
|
||||
"parent": null
|
||||
},
|
||||
"31": {
|
||||
"description": "Exit following a successful transaction.",
|
||||
"display_key": "ussd.exit_successful_transaction",
|
||||
"display_key": "ussd.kenya.exit_successful_transaction",
|
||||
"name": "exit_successful_transaction",
|
||||
"parent": null
|
||||
},
|
||||
"32": {
|
||||
"description": "End of a menu flow.",
|
||||
"display_key": "ussd.complete",
|
||||
"display_key": "ussd.kenya.complete",
|
||||
"name": "complete",
|
||||
"parent": null
|
||||
},
|
||||
"33": {
|
||||
"description": "Pin entry menu to view account balances.",
|
||||
"display_key": "ussd.account_balances_pin_authorization",
|
||||
"display_key": "ussd.kenya.account_balances_pin_authorization",
|
||||
"name": "account_balances_pin_authorization",
|
||||
"parent": "account_management"
|
||||
},
|
||||
"34": {
|
||||
"description": "Pin entry menu to view account statement.",
|
||||
"display_key": "ussd.account_statement_pin_authorization",
|
||||
"display_key": "ussd.kenya.account_statement_pin_authorization",
|
||||
"name": "account_statement_pin_authorization",
|
||||
"parent": "account_management"
|
||||
},
|
||||
"35": {
|
||||
"description": "Menu to display account balances.",
|
||||
"display_key": "ussd.account_balances",
|
||||
"display_key": "ussd.kenya.account_balances",
|
||||
"name": "account_balances",
|
||||
"parent": "account_management"
|
||||
},
|
||||
"36": {
|
||||
"description": "Menu to display first set of transactions in statement.",
|
||||
"display_key": "ussd.first_transaction_set",
|
||||
"display_key": "ussd.kenya.first_transaction_set",
|
||||
"name": "first_transaction_set",
|
||||
"parent": "account_management"
|
||||
"parent": null
|
||||
},
|
||||
"37": {
|
||||
"description": "Menu to display middle set of transactions in statement.",
|
||||
"display_key": "ussd.middle_transaction_set",
|
||||
"display_key": "ussd.kenya.middle_transaction_set",
|
||||
"name": "middle_transaction_set",
|
||||
"parent": null
|
||||
},
|
||||
"38": {
|
||||
"description": "Menu to display last set of transactions in statement.",
|
||||
"display_key": "ussd.last_transaction_set",
|
||||
"display_key": "ussd.kenya.last_transaction_set",
|
||||
"name": "last_transaction_set",
|
||||
"parent": null
|
||||
},
|
||||
"39": {
|
||||
"description": "Menu to instruct users to call the office.",
|
||||
"display_key": "ussd.help",
|
||||
"display_key": "ussd.kenya.help",
|
||||
"name": "help",
|
||||
"parent": null
|
||||
},
|
||||
"40": {
|
||||
"description": "Menu to display a user's entire profile",
|
||||
"display_key": "ussd.display_user_metadata",
|
||||
"display_key": "ussd.kenya.display_user_metadata",
|
||||
"name": "display_user_metadata",
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"41": {
|
||||
"description": "The recipient is not in the system",
|
||||
"display_key": "ussd.exit_invalid_recipient",
|
||||
"display_key": "ussd.kenya.exit_invalid_recipient",
|
||||
"name": "exit_invalid_recipient",
|
||||
"parent": null
|
||||
},
|
||||
"42": {
|
||||
"description": "Pin entry menu for changing name data.",
|
||||
"display_key": "ussd.name_edit_pin_authorization",
|
||||
"display_key": "ussd.kenya.name_edit_pin_authorization",
|
||||
"name": "name_edit_pin_authorization",
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"43": {
|
||||
"description": "Pin entry menu for changing gender data.",
|
||||
"display_key": "ussd.gender_edit_pin_authorization",
|
||||
"display_key": "ussd.kenya.gender_edit_pin_authorization",
|
||||
"name": "gender_edit_pin_authorization",
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"44": {
|
||||
"description": "Pin entry menu for changing location data.",
|
||||
"display_key": "ussd.location_edit_pin_authorization",
|
||||
"display_key": "ussd.kenya.location_edit_pin_authorization",
|
||||
"name": "location_edit_pin_authorization",
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"45": {
|
||||
"description": "Pin entry menu for changing products data.",
|
||||
"display_key": "ussd.products_edit_pin_authorization",
|
||||
"display_key": "ussd.kenya.products_edit_pin_authorization",
|
||||
"name": "products_edit_pin_authorization",
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"46": {
|
||||
"description": "Pin confirmation for pin change.",
|
||||
"display_key": "ussd.new_pin_confirmation",
|
||||
"display_key": "ussd.kenya.new_pin_confirmation",
|
||||
"name": "new_pin_confirmation",
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"47": {
|
||||
"description": "Year of birth entry menu.",
|
||||
"display_key": "ussd.enter_date_of_birth",
|
||||
"display_key": "ussd.kenya.enter_date_of_birth",
|
||||
"name": "enter_date_of_birth",
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"48": {
|
||||
"description": "Pin entry menu for changing year of birth data.",
|
||||
"display_key": "ussd.dob_edit_pin_authorization",
|
||||
"display_key": "ussd.kenya.dob_edit_pin_authorization",
|
||||
"name": "dob_edit_pin_authorization",
|
||||
"parent": "metadata_management"
|
||||
},
|
||||
"49": {
|
||||
"description": "Menu to display first set of tokens in the account's token list.",
|
||||
"display_key": "ussd.first_account_tokens_set",
|
||||
"display_key": "ussd.kenya.first_account_tokens_set",
|
||||
"name": "first_account_tokens_set",
|
||||
"parent": "start"
|
||||
"parent": null
|
||||
},
|
||||
"50": {
|
||||
"description": "Menu to display middle set of tokens in the account's token list.",
|
||||
"display_key": "ussd.middle_account_tokens_set",
|
||||
"display_key": "ussd.kenya.middle_account_tokens_set",
|
||||
"name": "middle_account_tokens_set",
|
||||
"parent": null
|
||||
},
|
||||
"51": {
|
||||
"description": "Menu to display last set of tokens in the account's token list.",
|
||||
"display_key": "ussd.last_account_tokens_set",
|
||||
"display_key": "ussd.kenya.last_account_tokens_set",
|
||||
"name": "last_account_tokens_set",
|
||||
"parent": null
|
||||
},
|
||||
"52": {
|
||||
"description": "Pin entry menu for setting an active token.",
|
||||
"display_key": "ussd.token_selection_pin_authorization",
|
||||
"display_key": "ussd.kenya.token_selection_pin_authorization",
|
||||
"name": "token_selection_pin_authorization",
|
||||
"parent": "first_account_tokens_set"
|
||||
"parent": null
|
||||
},
|
||||
"53": {
|
||||
"description": "Exit following a successful active token setting.",
|
||||
"display_key": "ussd.exit_successful_token_selection",
|
||||
"display_key": "ussd.kenya.exit_successful_token_selection",
|
||||
"name": "exit_successful_token_selection",
|
||||
"parent": null
|
||||
},
|
||||
"54": {
|
||||
"description": "Pin management menu for operations related to an account's pin.",
|
||||
"display_key": "ussd.pin_management",
|
||||
"display_key": "ussd.kenya.pin_management",
|
||||
"name": "pin_management",
|
||||
"parent": "start"
|
||||
},
|
||||
"55": {
|
||||
"description": "Phone number entry for account whose pin is being reset.",
|
||||
"display_key": "ussd.reset_guarded_pin",
|
||||
"display_key": "ussd.kenya.reset_guarded_pin",
|
||||
"name": "reset_guarded_pin",
|
||||
"parent": "pin_management"
|
||||
},
|
||||
"56": {
|
||||
"description": "Pin entry for initiating request to reset an account's pin.",
|
||||
"display_key": "ussd.reset_guarded_pin_authorization",
|
||||
"display_key": "ussd.kenya.reset_guarded_pin_authorization",
|
||||
"name": "reset_guarded_pin_authorization",
|
||||
"parent": "pin_management"
|
||||
},
|
||||
"57": {
|
||||
"description": "Exit menu following successful pin reset initiation.",
|
||||
"display_key": "ussd.exit_pin_reset_initiated_success",
|
||||
"display_key": "ussd.kenya.exit_pin_reset_initiated_success",
|
||||
"name": "exit_pin_reset_initiated_success",
|
||||
"parent": "pin_management"
|
||||
},
|
||||
"58": {
|
||||
"description": "Exit menu in the event that an account is not a set guardian.",
|
||||
"display_key": "ussd.exit_not_authorized_for_pin_reset",
|
||||
"display_key": "ussd.kenya.exit_not_authorized_for_pin_reset",
|
||||
"name": "exit_not_authorized_for_pin_reset",
|
||||
"parent": "pin_management"
|
||||
},
|
||||
"59": {
|
||||
"description": "Pin guard menu for handling guardianship operations.",
|
||||
"display_key": "ussd.guard_pin",
|
||||
"display_key": "ussd.kenya.guard_pin",
|
||||
"name": "guard_pin",
|
||||
"parent": "pin_management"
|
||||
},
|
||||
"60": {
|
||||
"description": "Pin entry to display a list of set guardians.",
|
||||
"display_key": "ussd.guardian_list_pin_authorization",
|
||||
"display_key": "ussd.kenya.guardian_list_pin_authorization",
|
||||
"name": "guardian_list_pin_authorization",
|
||||
"parent": "guard_pin"
|
||||
},
|
||||
"61": {
|
||||
"description": "Menu to display list of set guardians.",
|
||||
"display_key": "ussd.guardian_list",
|
||||
"display_key": "ussd.kenya.guardian_list",
|
||||
"name": "guardian_list",
|
||||
"parent": "guard_pin"
|
||||
},
|
||||
"62": {
|
||||
"description": "Phone number entry to add an account as a guardian to reset pin.",
|
||||
"display_key": "ussd.add_guardian",
|
||||
"display_key": "ussd.kenya.add_guardian",
|
||||
"name": "add_guardian",
|
||||
"parent": "guard_pin"
|
||||
},
|
||||
"63": {
|
||||
"description": "Pin entry to confirm addition of an account as a guardian.",
|
||||
"display_key": "ussd.add_guardian_pin_authorization",
|
||||
"display_key": "ussd.kenya.add_guardian_pin_authorization",
|
||||
"name": "add_guardian_pin_authorization",
|
||||
"parent": "guard_pin"
|
||||
},
|
||||
"64": {
|
||||
"description": "Exit menu when an account is successfully added as pin reset guardian.",
|
||||
"display_key": "ussd.exit_guardian_addition_success",
|
||||
"display_key": "ussd.kenya.exit_guardian_addition_success",
|
||||
"name": "exit_guardian_addition_success",
|
||||
"parent": "guard_pin"
|
||||
},
|
||||
"65": {
|
||||
"description": "Phone number entry to remove an account as a guardian to reset pin.",
|
||||
"display_key": "ussd.remove_guardian",
|
||||
"display_key": "ussd.kenya.remove_guardian",
|
||||
"name": "remove_guardian",
|
||||
"parent": "guard_pin"
|
||||
},
|
||||
"66": {
|
||||
"description": "Pin entry to confirm removal of an account as a guardian.",
|
||||
"display_key": "ussd.remove_guardian_pin_authorization",
|
||||
"display_key": "ussd.kenya.remove_guardian_pin_authorization",
|
||||
"name": "remove_guardian_pin_authorization",
|
||||
"parent": "guard_pin"
|
||||
},
|
||||
"67": {
|
||||
"description": "Exit menu when an account is successfully removed as pin reset guardian.",
|
||||
"display_key": "ussd.exit_guardian_removal_success",
|
||||
"display_key": "ussd.kenya.exit_guardian_removal_success",
|
||||
"name": "exit_guardian_removal_success",
|
||||
"parent": "guard_pin"
|
||||
},
|
||||
"68": {
|
||||
"description": "Exit menu when invalid phone number entry for guardian addition.",
|
||||
"display_key": "ussd.exit_invalid_guardian_addition",
|
||||
"description": "Exit menu when invalid phone number entry for guardian addition. ",
|
||||
"display_key": "ussd.kenya.exit_invalid_guardian_addition",
|
||||
"name": "exit_invalid_guardian_addition",
|
||||
"parent": "guard_pin"
|
||||
},
|
||||
"69": {
|
||||
"description": "Exit menu when invalid phone number entry for guardian removal.",
|
||||
"display_key": "ussd.exit_invalid_guardian_removal",
|
||||
"description": "Exit menu when invalid phone number entry for guardian removal. ",
|
||||
"display_key": "ussd.kenya.exit_invalid_guardian_removal",
|
||||
"name": "exit_invalid_guardian_removal",
|
||||
"parent": "guard_pin"
|
||||
},
|
||||
"70": {
|
||||
"description": "Menu to display middle set of languages to select.",
|
||||
"display_key": "ussd.initial_middle_language_set",
|
||||
"name": "initial_middle_language_set",
|
||||
"parent": null
|
||||
},
|
||||
"71": {
|
||||
"description": "Menu to display last set of languages to select.",
|
||||
"display_key": "ussd.initial_last_language_set",
|
||||
"name": "initial_last_language_set",
|
||||
"parent": null
|
||||
},
|
||||
"72": {
|
||||
"description": "Menu to display middle set of languages to select.",
|
||||
"display_key": "ussd.middle_language_set",
|
||||
"name": "middle_language_set",
|
||||
"parent": null
|
||||
},
|
||||
"73": {
|
||||
"description": "Menu to display last set of languages to select.",
|
||||
"display_key": "ussd.last_language_set",
|
||||
"name": "last_language_set",
|
||||
"parent": null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,4 +7,3 @@ from .custom import CustomMetadata
|
||||
from .person import PersonMetadata
|
||||
from .phone import PhonePointerMetadata
|
||||
from .preferences import PreferencesMetadata
|
||||
from .tokens import TokenMetadata
|
||||
|
||||
@@ -19,33 +19,34 @@ from cic_ussd.account.metadata import get_cached_preferred_language
|
||||
from cic_ussd.account.statement import (
|
||||
get_cached_statement,
|
||||
parse_statement_transactions,
|
||||
query_statement)
|
||||
query_statement,
|
||||
statement_transaction_set
|
||||
)
|
||||
from cic_ussd.account.tokens import (create_account_tokens_list,
|
||||
get_active_token_symbol,
|
||||
get_cached_token_data,
|
||||
get_cached_token_symbol_list,
|
||||
get_cached_token_data_list,
|
||||
parse_token_list)
|
||||
parse_token_list,
|
||||
token_list_set)
|
||||
from cic_ussd.account.transaction import from_wei, to_wei
|
||||
from cic_ussd.cache import cache_data_key, cache_data, get_cached_data
|
||||
from cic_ussd.cache import cache_data_key, cache_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.util import parse_person_metadata
|
||||
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
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
logg = logging.getLogger(__file__)
|
||||
logg = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MenuProcessor:
|
||||
def __init__(self, account: Account, display_key: str, menu_name: str, session: Session, ussd_session: dict):
|
||||
self.account = account
|
||||
self.display_key = display_key
|
||||
if account:
|
||||
self.identifier = bytes.fromhex(self.account.blockchain_address)
|
||||
self.identifier = bytes.fromhex(self.account.blockchain_address)
|
||||
self.menu_name = menu_name
|
||||
self.session = session
|
||||
self.ussd_session = ussd_session
|
||||
@@ -88,29 +89,36 @@ class MenuProcessor:
|
||||
:rtype:
|
||||
"""
|
||||
cached_statement = get_cached_statement(self.account.blockchain_address)
|
||||
|
||||
transaction_sets = []
|
||||
if cached_statement:
|
||||
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)]
|
||||
preferred_language = get_cached_preferred_language(self.account.blockchain_address)
|
||||
if not preferred_language:
|
||||
preferred_language = i18n.config.get('fallback')
|
||||
no_transaction_history = statement_transaction_set(preferred_language, transaction_sets)
|
||||
first_transaction_set = no_transaction_history
|
||||
middle_transaction_set = no_transaction_history
|
||||
last_transaction_set = no_transaction_history
|
||||
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])
|
||||
|
||||
statement_list = []
|
||||
if cached_statement:
|
||||
statement_list = parse_statement_transactions(statement=json.loads(cached_statement))
|
||||
|
||||
fallback = translation_for('helpers.no_transaction_history', preferred_language)
|
||||
transaction_sets = ussd_menu_list(fallback=fallback, menu_list=statement_list, split=3)
|
||||
|
||||
if self.display_key == 'ussd.first_transaction_set':
|
||||
if self.display_key == 'ussd.kenya.first_transaction_set':
|
||||
return translation_for(
|
||||
self.display_key, preferred_language, first_transaction_set=transaction_sets[0]
|
||||
self.display_key, preferred_language, first_transaction_set=first_transaction_set
|
||||
)
|
||||
if self.display_key == 'ussd.middle_transaction_set':
|
||||
if self.display_key == 'ussd.kenya.middle_transaction_set':
|
||||
return translation_for(
|
||||
self.display_key, preferred_language, middle_transaction_set=transaction_sets[1]
|
||||
self.display_key, preferred_language, middle_transaction_set=middle_transaction_set
|
||||
)
|
||||
if self.display_key == 'ussd.last_transaction_set':
|
||||
if self.display_key == 'ussd.kenya.last_transaction_set':
|
||||
return translation_for(
|
||||
self.display_key, preferred_language, last_transaction_set=transaction_sets[2]
|
||||
self.display_key, preferred_language, last_transaction_set=last_transaction_set
|
||||
)
|
||||
|
||||
def add_guardian_pin_authorization(self):
|
||||
@@ -121,7 +129,7 @@ class MenuProcessor:
|
||||
preferred_language = get_cached_preferred_language(self.account.blockchain_address)
|
||||
if not preferred_language:
|
||||
preferred_language = i18n.config.get('fallback')
|
||||
set_guardians = self.account.get_guardians()[:3]
|
||||
set_guardians = self.account.get_guardians()
|
||||
if set_guardians:
|
||||
guardians_list = ''
|
||||
guardians_list_header = translation_for('helpers.guardians_list_header', preferred_language)
|
||||
@@ -137,30 +145,36 @@ class MenuProcessor:
|
||||
def account_tokens(self) -> str:
|
||||
cached_token_data_list = get_cached_token_data_list(self.account.blockchain_address)
|
||||
token_data_list = parse_token_list(cached_token_data_list)
|
||||
|
||||
token_list_sets = [token_data_list[tds:tds + 3] for tds in range(0, len(token_data_list), 3)]
|
||||
preferred_language = get_cached_preferred_language(self.account.blockchain_address)
|
||||
if not preferred_language:
|
||||
preferred_language = i18n.config.get('fallback')
|
||||
no_token_list = token_list_set(preferred_language, [])
|
||||
first_account_tokens_set = no_token_list
|
||||
middle_account_tokens_set = no_token_list
|
||||
last_account_tokens_set = no_token_list
|
||||
if token_list_sets:
|
||||
data = {
|
||||
'account_tokens_list': cached_token_data_list
|
||||
}
|
||||
save_session_data(data=data, queue='cic-ussd', session=self.session, ussd_session=self.ussd_session)
|
||||
first_account_tokens_set = token_list_set(preferred_language, token_list_sets[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)
|
||||
|
||||
data = {
|
||||
'account_tokens_list': cached_token_data_list
|
||||
}
|
||||
save_session_data(data=data, queue='cic-ussd', session=self.session, ussd_session=self.ussd_session)
|
||||
|
||||
if self.display_key == 'ussd.first_account_tokens_set':
|
||||
if len(token_list_sets) >= 2:
|
||||
middle_account_tokens_set = token_list_set(preferred_language, token_list_sets[1])
|
||||
if len(token_list_sets) >= 3:
|
||||
last_account_tokens_set = token_list_set(preferred_language, token_list_sets[2])
|
||||
if self.display_key == 'ussd.kenya.first_account_tokens_set':
|
||||
return translation_for(
|
||||
self.display_key, preferred_language, first_account_tokens_set=token_list_sets[0]
|
||||
self.display_key, preferred_language, first_account_tokens_set=first_account_tokens_set
|
||||
)
|
||||
if self.display_key == 'ussd.middle_account_tokens_set':
|
||||
if self.display_key == 'ussd.kenya.middle_account_tokens_set':
|
||||
return translation_for(
|
||||
self.display_key, preferred_language, middle_account_tokens_set=token_list_sets[1]
|
||||
self.display_key, preferred_language, middle_account_tokens_set=middle_account_tokens_set
|
||||
)
|
||||
if self.display_key == 'ussd.last_account_tokens_set':
|
||||
if self.display_key == 'ussd.kenya.last_account_tokens_set':
|
||||
return translation_for(
|
||||
self.display_key, preferred_language, last_account_tokens_set=token_list_sets[2]
|
||||
self.display_key, preferred_language, last_account_tokens_set=last_account_tokens_set
|
||||
)
|
||||
|
||||
def help(self) -> str:
|
||||
@@ -208,7 +222,7 @@ class MenuProcessor:
|
||||
remaining_attempts = 3
|
||||
remaining_attempts -= self.account.failed_pin_attempts
|
||||
retry_pin_entry = translation_for(
|
||||
'ussd.retry_pin_entry', preferred_language, remaining_attempts=remaining_attempts
|
||||
'ussd.kenya.retry_pin_entry', preferred_language, remaining_attempts=remaining_attempts
|
||||
)
|
||||
return translation_for(
|
||||
f'{self.display_key}.retry', preferred_language, retry_pin_entry=retry_pin_entry
|
||||
@@ -224,38 +238,6 @@ class MenuProcessor:
|
||||
guardian = Account.get_by_phone_number(guardian_phone_number, self.session)
|
||||
return guardian.standard_metadata_id()
|
||||
|
||||
def language(self):
|
||||
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)
|
||||
|
||||
if self.account:
|
||||
preferred_language = get_cached_preferred_language(self.account.blockchain_address)
|
||||
else:
|
||||
preferred_language = i18n.config.get('fallback')
|
||||
|
||||
fallback = translation_for('helpers.no_language_list', preferred_language)
|
||||
language_list_sets = ussd_menu_list(fallback=fallback, menu_list=language_list, split=3)
|
||||
|
||||
if self.display_key in ['ussd.initial_language_selection', 'ussd.select_preferred_language']:
|
||||
return translation_for(
|
||||
self.display_key, preferred_language, first_language_set=language_list_sets[0]
|
||||
)
|
||||
|
||||
if 'middle_language_set' in self.display_key:
|
||||
return translation_for(
|
||||
self.display_key, preferred_language, middle_language_set=language_list_sets[1]
|
||||
)
|
||||
|
||||
if 'last_language_set' in self.display_key:
|
||||
return translation_for(
|
||||
self.display_key, preferred_language, last_language_set=language_list_sets[2]
|
||||
)
|
||||
|
||||
def account_creation_prompt(self):
|
||||
preferred_language = preferred_langauge_from_selection(self.ussd_session.get('user_input'))
|
||||
return translation_for(self.display_key, preferred_language)
|
||||
|
||||
def reset_guarded_pin_authorization(self):
|
||||
guarded_account_information = self.guarded_account_metadata()
|
||||
return self.pin_authorization(guarded_account_information=guarded_account_information)
|
||||
@@ -399,9 +381,8 @@ class MenuProcessor:
|
||||
)
|
||||
|
||||
def exit_invalid_menu_option(self):
|
||||
if self.account:
|
||||
preferred_language = get_cached_preferred_language(self.account.blockchain_address)
|
||||
else:
|
||||
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, support_phone=Support.phone_number)
|
||||
|
||||
@@ -409,7 +390,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('ussd.exit_pin_blocked', preferred_language, support_phone=Support.phone_number)
|
||||
return translation_for('ussd.kenya.exit_pin_blocked', preferred_language, support_phone=Support.phone_number)
|
||||
|
||||
def exit_successful_token_selection(self) -> str:
|
||||
selected_token = self.ussd_session.get('data').get('selected_token')
|
||||
@@ -417,7 +398,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):
|
||||
"""
|
||||
@@ -464,9 +445,6 @@ def response(account: Account, display_key: str, menu_name: str, session: Sessio
|
||||
"""
|
||||
menu_processor = MenuProcessor(account, display_key, menu_name, session, ussd_session)
|
||||
|
||||
if menu_name == 'account_creation_prompt':
|
||||
return menu_processor.account_creation_prompt()
|
||||
|
||||
if menu_name == 'start':
|
||||
return menu_processor.start_menu()
|
||||
|
||||
@@ -524,9 +502,6 @@ def response(account: Account, display_key: str, menu_name: str, session: Sessio
|
||||
if 'account_tokens_set' in menu_name:
|
||||
return menu_processor.account_tokens()
|
||||
|
||||
if 'language' in menu_name:
|
||||
return menu_processor.language()
|
||||
|
||||
if menu_name == 'display_user_metadata':
|
||||
return menu_processor.person_metadata()
|
||||
|
||||
@@ -540,4 +515,5 @@ def response(account: Account, display_key: str, menu_name: str, session: Sessio
|
||||
return menu_processor.exit_successful_token_selection()
|
||||
|
||||
preferred_language = get_cached_preferred_language(account.blockchain_address)
|
||||
|
||||
return translation_for(display_key, preferred_language)
|
||||
|
||||
@@ -8,7 +8,7 @@ from sqlalchemy.orm.session import Session
|
||||
from tinydb.table import Document
|
||||
|
||||
# local imports
|
||||
from cic_ussd.db.models.account import Account
|
||||
from cic_ussd.db.models.account import Account, create
|
||||
from cic_ussd.db.models.base import SessionBase
|
||||
from cic_ussd.db.models.ussd_session import UssdSession
|
||||
from cic_ussd.menu.ussd_menu import UssdMenu
|
||||
@@ -16,6 +16,7 @@ from cic_ussd.processor.menu import response
|
||||
from cic_ussd.processor.util import latest_input, resume_last_ussd_session
|
||||
from cic_ussd.session.ussd_session import create_or_update_session, persist_ussd_session
|
||||
from cic_ussd.state_machine import UssdStateMachine
|
||||
from cic_ussd.translation import translation_for
|
||||
from cic_ussd.validator import is_valid_response
|
||||
|
||||
|
||||
@@ -35,6 +36,9 @@ def handle_menu(account: Account, session: Session) -> Document:
|
||||
last_ussd_session = UssdSession.last_ussd_session(account.phone_number, session)
|
||||
if last_ussd_session:
|
||||
return resume_last_ussd_session(last_ussd_session.state)
|
||||
|
||||
elif not account.has_preferred_language():
|
||||
return UssdMenu.find_by_name('initial_language_selection')
|
||||
else:
|
||||
return UssdMenu.find_by_name('initial_pin_entry')
|
||||
|
||||
@@ -67,13 +71,16 @@ def get_menu(account: Account,
|
||||
return UssdMenu.find_by_name(state)
|
||||
|
||||
|
||||
def handle_menu_operations(external_session_id: str,
|
||||
def handle_menu_operations(chain_str: str,
|
||||
external_session_id: str,
|
||||
phone_number: str,
|
||||
queue: str,
|
||||
service_code: str,
|
||||
session,
|
||||
user_input: str):
|
||||
"""
|
||||
:param chain_str:
|
||||
:type chain_str:
|
||||
:param external_session_id:
|
||||
:type external_session_id:
|
||||
:param phone_number:
|
||||
@@ -93,38 +100,10 @@ def handle_menu_operations(external_session_id: str,
|
||||
account: Account = Account.get_by_phone_number(phone_number, session)
|
||||
if account:
|
||||
return handle_account_menu_operations(account, external_session_id, queue, session, service_code, user_input)
|
||||
else:
|
||||
return handle_no_account_menu_operations(
|
||||
account, external_session_id, phone_number, queue, session, service_code, user_input)
|
||||
|
||||
|
||||
def handle_no_account_menu_operations(account: Optional[Account],
|
||||
external_session_id: str,
|
||||
phone_number: str,
|
||||
queue: str,
|
||||
session: Session,
|
||||
service_code: str,
|
||||
user_input: str):
|
||||
"""
|
||||
:param account:
|
||||
:type account:
|
||||
:param external_session_id:
|
||||
:type external_session_id:
|
||||
:param phone_number:
|
||||
:type phone_number:
|
||||
:param queue:
|
||||
:type queue:
|
||||
:param session:
|
||||
:type session:
|
||||
:param service_code:
|
||||
:type service_code:
|
||||
:param user_input:
|
||||
:type user_input:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
menu = UssdMenu.find_by_name('initial_language_selection')
|
||||
ussd_session = create_or_update_session(
|
||||
create(chain_str, phone_number, session)
|
||||
menu = UssdMenu.find_by_name('account_creation_prompt')
|
||||
preferred_language = i18n.config.get('fallback')
|
||||
create_or_update_session(
|
||||
external_session_id=external_session_id,
|
||||
msisdn=phone_number,
|
||||
service_code=service_code,
|
||||
@@ -132,20 +111,7 @@ def handle_no_account_menu_operations(account: Optional[Account],
|
||||
session=session,
|
||||
user_input=user_input)
|
||||
persist_ussd_session(external_session_id, queue)
|
||||
last_ussd_session: UssdSession = UssdSession.last_ussd_session(phone_number, session)
|
||||
if last_ussd_session:
|
||||
if not user_input:
|
||||
menu = resume_last_ussd_session(last_ussd_session.state)
|
||||
else:
|
||||
session = SessionBase.bind_session(session)
|
||||
state = next_state(account, session, user_input, last_ussd_session.to_json())
|
||||
menu = UssdMenu.find_by_name(state)
|
||||
|
||||
return response(account=account,
|
||||
display_key=menu.get('display_key'),
|
||||
menu_name=menu.get('name'),
|
||||
session=session,
|
||||
ussd_session=ussd_session.to_json())
|
||||
return translation_for('ussd.kenya.account_creation_prompt', preferred_language)
|
||||
|
||||
|
||||
def handle_account_menu_operations(account: Account,
|
||||
@@ -186,12 +152,15 @@ def handle_account_menu_operations(account: Account,
|
||||
if last_ussd_session:
|
||||
ussd_session = create_or_update_session(
|
||||
external_session_id, phone_number, service_code, user_input, menu.get('name'), session,
|
||||
last_ussd_session.data)
|
||||
last_ussd_session.data
|
||||
)
|
||||
else:
|
||||
ussd_session = create_or_update_session(
|
||||
external_session_id, phone_number, service_code, user_input, menu.get('name'), session, {})
|
||||
external_session_id, phone_number, service_code, user_input, menu.get('name'), session, None
|
||||
)
|
||||
menu_response = response(
|
||||
account, menu.get('display_key'), menu.get('name'), session, ussd_session.to_json())
|
||||
account, menu.get('display_key'), menu.get('name'), session, ussd_session.to_json()
|
||||
)
|
||||
if not is_valid_response(menu_response):
|
||||
raise ValueError(f'Invalid response: {response}')
|
||||
persist_ussd_session(external_session_id, queue)
|
||||
|
||||
@@ -3,7 +3,7 @@ import datetime
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from typing import List, Union
|
||||
from typing import Union
|
||||
|
||||
# external imports
|
||||
from cic_types.condiments import MetadataPointer
|
||||
@@ -21,7 +21,9 @@ logg = logging.getLogger(__file__)
|
||||
def latest_input(user_input: str) -> str:
|
||||
"""
|
||||
:param user_input:
|
||||
:type user_input:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
return user_input.split('*')[-1]
|
||||
|
||||
@@ -83,27 +85,6 @@ def resume_last_ussd_session(last_state: str) -> Document:
|
||||
return UssdMenu.find_by_name(last_state)
|
||||
|
||||
|
||||
def ussd_menu_list(fallback: str, menu_list: list, split: int = 3) -> List[str]:
|
||||
"""
|
||||
:param fallback:
|
||||
:type fallback:
|
||||
:param menu_list:
|
||||
:type menu_list:
|
||||
:param split:
|
||||
:type split:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
menu_list_sets = [menu_list[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)
|
||||
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:
|
||||
@@ -151,28 +132,17 @@ def wait_for_session_data(resource_name: str, session_data_key: str, ussd_sessio
|
||||
: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...')
|
||||
session_data = ussd_session.get('data').get(session_data_key)
|
||||
counter = 0
|
||||
while session_data is None:
|
||||
logg.debug(f'Waiting for: {resource_name}. Checking after: {interval} ...')
|
||||
time.sleep(interval)
|
||||
data_poller += 1
|
||||
if data:
|
||||
logg.debug(f'Data object found, proceeding to poll for: {session_data_key}')
|
||||
counter += 1
|
||||
session_data = ussd_session.get('data').get(session_data_key)
|
||||
if session_data is not None:
|
||||
logg.debug(f'{resource_name} now available.')
|
||||
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.')
|
||||
else:
|
||||
if counter == max_retry:
|
||||
logg.debug(f'Could not find: {resource_name} within: {max_retry}')
|
||||
break
|
||||
|
||||
elif session_data_poller >= max_retry:
|
||||
logg.debug(f'Could not find data object within: {max_retry}')
|
||||
|
||||
@@ -20,7 +20,6 @@ from cic_ussd.db import dsn_from_config
|
||||
from cic_ussd.db.models.base import SessionBase
|
||||
from cic_ussd.phone_number import Support
|
||||
from cic_ussd.session.ussd_session import UssdSession as InMemoryUssdSession
|
||||
from cic_ussd.translation import generate_locale_files
|
||||
from cic_ussd.validator import validate_presence
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
@@ -84,10 +83,6 @@ if key_file_path:
|
||||
validate_presence(path=key_file_path)
|
||||
Signer.key_file_path = key_file_path
|
||||
|
||||
generate_locale_files(locale_dir=config.get('LOCALE_PATH'),
|
||||
schema_file_path=config.get('SCHEMA_FILE_PATH'),
|
||||
translation_builder_path=config.get('LOCALE_FILE_BUILDERS'))
|
||||
|
||||
# set up translations
|
||||
i18n.load_path.append(config.get('LOCALE_PATH'))
|
||||
i18n.set('fallback', config.get('LOCALE_FALLBACK'))
|
||||
|
||||
@@ -18,7 +18,6 @@ from cic_types.ext.metadata.signer import Signer
|
||||
|
||||
# local imports
|
||||
from cic_ussd.account.chain import Chain
|
||||
from cic_ussd.account.guardianship import Guardianship
|
||||
from cic_ussd.account.tokens import query_default_token
|
||||
from cic_ussd.cache import cache_data, cache_data_key, Cache
|
||||
from cic_ussd.db import dsn_from_config
|
||||
@@ -34,7 +33,7 @@ from cic_ussd.processor.ussd import handle_menu_operations
|
||||
from cic_ussd.runnable.server_base import exportable_parser, logg
|
||||
from cic_ussd.session.ussd_session import UssdSession as InMemoryUssdSession
|
||||
from cic_ussd.state_machine import UssdStateMachine
|
||||
from cic_ussd.translation import generate_locale_files, Languages, translation_for
|
||||
from cic_ussd.translation import translation_for
|
||||
from cic_ussd.validator import check_ip, check_request_content_length, validate_phone_number, validate_presence
|
||||
|
||||
args = exportable_parser.parse_args()
|
||||
@@ -57,6 +56,10 @@ SessionBase.connect(data_source_name,
|
||||
pool_size=int(config.get('DATABASE_POOL_SIZE')),
|
||||
debug=config.true('DATABASE_DEBUG'))
|
||||
|
||||
# set up translations
|
||||
i18n.load_path.append(config.get('LOCALE_PATH'))
|
||||
i18n.set('fallback', config.get('LOCALE_FALLBACK'))
|
||||
|
||||
# set Fernet key
|
||||
PasswordEncoder.set_key(config.get('APP_PASSWORD_PEPPER'))
|
||||
|
||||
@@ -118,22 +121,6 @@ valid_service_codes = config.get('USSD_SERVICE_CODE').split(",")
|
||||
E164Format.region = config.get('E164_REGION')
|
||||
Support.phone_number = config.get('OFFICE_SUPPORT_PHONE')
|
||||
|
||||
validate_presence(config.get('SYSTEM_GUARDIANS_FILE'))
|
||||
Guardianship.load_system_guardians(config.get('SYSTEM_GUARDIANS_FILE'))
|
||||
|
||||
generate_locale_files(locale_dir=config.get('LOCALE_PATH'),
|
||||
schema_file_path=config.get('SCHEMA_FILE_PATH'),
|
||||
translation_builder_path=config.get('LOCALE_FILE_BUILDERS'))
|
||||
|
||||
# set up translations
|
||||
i18n.load_path.append(config.get('LOCALE_PATH'))
|
||||
i18n.set('fallback', config.get('LOCALE_FALLBACK'))
|
||||
|
||||
validate_presence(config.get('LANGUAGES_FILE'))
|
||||
Languages.load_languages_dict(config.get('LANGUAGES_FILE'))
|
||||
languages = Languages()
|
||||
languages.cache_system_languages()
|
||||
|
||||
|
||||
def application(env, start_response):
|
||||
"""Loads python code for application to be accessible over web server
|
||||
@@ -188,7 +175,7 @@ def application(env, start_response):
|
||||
|
||||
if service_code not in valid_service_codes:
|
||||
response = translation_for(
|
||||
'ussd.invalid_service_code',
|
||||
'ussd.kenya.invalid_service_code',
|
||||
i18n.config.get('fallback'),
|
||||
valid_service_code=valid_service_codes[0]
|
||||
)
|
||||
@@ -202,7 +189,9 @@ def application(env, start_response):
|
||||
return []
|
||||
logg.debug('session {} started for {}'.format(external_session_id, phone_number))
|
||||
|
||||
response = handle_menu_operations(external_session_id, phone_number, args.q, service_code, session, user_input)
|
||||
response = handle_menu_operations(
|
||||
chain_str, external_session_id, phone_number, args.q, service_code, session, user_input
|
||||
)
|
||||
response_bytes, headers = with_content_headers(headers, response)
|
||||
start_response('200 OK,', headers)
|
||||
session.commit()
|
||||
|
||||
@@ -11,20 +11,46 @@ from cic_types.models.person import get_contact_data_from_vcard, generate_vcard_
|
||||
|
||||
# local imports
|
||||
from cic_ussd.account.chain import Chain
|
||||
from cic_ussd.account.maps import gender
|
||||
from cic_ussd.account.maps import gender, language
|
||||
from cic_ussd.account.metadata import get_cached_preferred_language
|
||||
from cic_ussd.db.models.account import Account, create
|
||||
from cic_ussd.db.models.account import Account
|
||||
from cic_ussd.db.models.base import SessionBase
|
||||
from cic_ussd.error import MetadataNotFoundError
|
||||
from cic_ussd.metadata import PersonMetadata
|
||||
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
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
logg = logging.getLogger(__file__)
|
||||
|
||||
|
||||
def change_preferred_language(state_machine_data: Tuple[str, dict, Account, Session]):
|
||||
"""
|
||||
:param state_machine_data:
|
||||
:type state_machine_data:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
user_input, ussd_session, account, session = state_machine_data
|
||||
r_user_input = language().get(user_input)
|
||||
session = SessionBase.bind_session(session)
|
||||
account.preferred_language = r_user_input
|
||||
session.add(account)
|
||||
session.flush()
|
||||
SessionBase.release_session(session)
|
||||
|
||||
preferences_data = {
|
||||
'preferred_language': r_user_input
|
||||
}
|
||||
|
||||
s = celery.signature(
|
||||
'cic_ussd.tasks.metadata.add_preferences_metadata',
|
||||
[account.blockchain_address, preferences_data],
|
||||
queue='cic-ussd'
|
||||
)
|
||||
return s.apply_async()
|
||||
|
||||
|
||||
def update_account_status_to_active(state_machine_data: Tuple[str, dict, Account, Session]):
|
||||
"""This function sets user's account to active.
|
||||
:param state_machine_data: A tuple containing user input, a ussd session and user object.
|
||||
@@ -219,16 +245,3 @@ def edit_user_metadata_attribute(state_machine_data: Tuple[str, dict, Account, S
|
||||
[blockchain_address, parsed_person_metadata]
|
||||
)
|
||||
s_edit_person_metadata.apply_async(queue='cic-ussd')
|
||||
|
||||
|
||||
def process_account_creation(state_machine_data: Tuple[str, dict, Account, Session]):
|
||||
"""
|
||||
:param state_machine_data:
|
||||
:type state_machine_data:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
user_input, ussd_session, account, session = state_machine_data
|
||||
preferred_language = preferred_langauge_from_selection(user_input=user_input)
|
||||
chain_str = Chain.spec.__str__()
|
||||
create(chain_str, ussd_session.get('msisdn'), session, preferred_language)
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
# standard imports
|
||||
import json
|
||||
from typing import Tuple
|
||||
|
||||
# external imports
|
||||
import celery
|
||||
import i18n
|
||||
from cic_types.condiments import MetadataPointer
|
||||
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.session.ussd_session import save_session_data
|
||||
from cic_ussd.translation import Languages
|
||||
|
||||
|
||||
def is_valid_language_selection(state_machine_data: Tuple[str, dict, Account, Session]):
|
||||
"""
|
||||
:param state_machine_data:
|
||||
:type state_machine_data:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
user_input, ussd_session, account, session = state_machine_data
|
||||
|
||||
key = cache_data_key('system:languages'.encode('utf-8'), MetadataPointer.NONE)
|
||||
cached_system_languages = get_cached_data(key)
|
||||
language_list = json.loads(cached_system_languages)
|
||||
|
||||
if not language_list:
|
||||
wait_for_cache(identifier='system:languages'.encode('utf-8'), resource_name='Languages list', salt=MetadataPointer.NONE)
|
||||
|
||||
if user_input in ['00', '11', '22']:
|
||||
return False
|
||||
user_input = int(user_input)
|
||||
return user_input <= len(language_list)
|
||||
|
||||
|
||||
def change_preferred_language(state_machine_data: Tuple[str, dict, Account, Session]):
|
||||
"""
|
||||
:param state_machine_data:
|
||||
:type state_machine_data:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
process_language_selection(state_machine_data=state_machine_data)
|
||||
user_input, ussd_session, account, session = state_machine_data
|
||||
wait_for_session_data(resource_name='Preferred language', session_data_key='preferred_language', ussd_session=ussd_session)
|
||||
preferred_language = ussd_session.get('data').get('preferred_language')
|
||||
preferences_data = {
|
||||
'preferred_language': preferred_language
|
||||
}
|
||||
|
||||
s = celery.signature(
|
||||
'cic_ussd.tasks.metadata.add_preferences_metadata',
|
||||
[account.blockchain_address, preferences_data],
|
||||
queue='cic-ussd'
|
||||
)
|
||||
return s.apply_async()
|
||||
|
||||
|
||||
def process_language_selection(state_machine_data: Tuple[str, dict, Account, Session]):
|
||||
"""
|
||||
:param state_machine_data:
|
||||
:type state_machine_data:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
user_input, ussd_session, account, session = state_machine_data
|
||||
preferred_language = preferred_langauge_from_selection(user_input=user_input)
|
||||
data = {
|
||||
'preferred_language': preferred_language
|
||||
}
|
||||
save_session_data(queue='cic-ussd', session=session, data=data, ussd_session=ussd_session)
|
||||
|
||||
|
||||
def preferred_langauge_from_selection(user_input: str):
|
||||
"""
|
||||
:param user_input:
|
||||
:type user_input:
|
||||
:return:
|
||||
:rtype:
|
||||
"""
|
||||
key = cache_data_key('system:languages'.encode('utf-8'), MetadataPointer.NONE)
|
||||
cached_system_languages = get_cached_data(key)
|
||||
language_list = json.loads(cached_system_languages)
|
||||
user_input = int(user_input)
|
||||
selected_language = language_list[user_input - 1]
|
||||
preferred_language = i18n.config.get('fallback')
|
||||
for key, value in Languages.languages_dict.items():
|
||||
if selected_language[3:] == value:
|
||||
preferred_language = key
|
||||
return preferred_language
|
||||
@@ -9,11 +9,9 @@ from phonenumbers.phonenumberutil import NumberParseException
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
# local imports
|
||||
from cic_ussd.account.guardianship import Guardianship
|
||||
from cic_ussd.account.metadata import get_cached_preferred_language
|
||||
from cic_ussd.db.models.account import Account
|
||||
from cic_ussd.db.models.base import SessionBase
|
||||
from cic_ussd.notifications import Notifier
|
||||
from cic_ussd.phone_number import process_phone_number, E164Format
|
||||
from cic_ussd.session.ussd_session import save_session_data
|
||||
from cic_ussd.translation import translation_for
|
||||
@@ -84,10 +82,8 @@ def is_valid_guardian_addition(state_machine_data: Tuple[str, dict, Account, Ses
|
||||
preferred_language = i18n.config.get('fallback')
|
||||
|
||||
is_valid_account = Account.get_by_phone_number(phone_number, session) is not None
|
||||
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() or is_system_guardian
|
||||
is_existent_guardian = phone_number in account.get_guardians()
|
||||
|
||||
failure_reason = ''
|
||||
if not is_valid_account:
|
||||
@@ -104,7 +100,7 @@ def is_valid_guardian_addition(state_machine_data: Tuple[str, dict, Account, Ses
|
||||
session_data['failure_reason'] = failure_reason
|
||||
save_session_data('cic-ussd', session, session_data, ussd_session)
|
||||
|
||||
return phone_number is not None and is_valid_account and not is_existent_guardian and not is_initiator and not is_system_guardian
|
||||
return phone_number is not None and is_valid_account and not is_existent_guardian and not is_initiator
|
||||
|
||||
|
||||
def add_pin_guardian(state_machine_data: Tuple[str, dict, Account, Session]):
|
||||
@@ -134,9 +130,6 @@ def is_set_pin_guardian(account: Account, checked_number: str, preferred_languag
|
||||
is_set_guardian = checked_number in set_guardians
|
||||
is_initiator = checked_number == account.phone_number
|
||||
|
||||
guardianship = Guardianship()
|
||||
is_system_guardian = guardianship.is_system_guardian(checked_number)
|
||||
|
||||
if not is_set_guardian:
|
||||
failure_reason = translation_for('helpers.error.is_not_existent_guardian', preferred_language)
|
||||
|
||||
@@ -148,7 +141,7 @@ def is_set_pin_guardian(account: Account, checked_number: str, preferred_languag
|
||||
session_data['failure_reason'] = failure_reason
|
||||
save_session_data('cic-ussd', session, session_data, ussd_session)
|
||||
|
||||
return (is_set_guardian or is_system_guardian) and not is_initiator
|
||||
return is_set_guardian and not is_initiator
|
||||
|
||||
|
||||
def is_dialers_pin_guardian(state_machine_data: Tuple[str, dict, Account, Session]):
|
||||
@@ -200,20 +193,8 @@ def initiate_pin_reset(state_machine_data: Tuple[str, dict, Account, Session]):
|
||||
save_session_data('cic-ussd', session, session_data, ussd_session)
|
||||
guarded_account_phone_number = session_data.get('guarded_account_phone_number')
|
||||
guarded_account = Account.get_by_phone_number(guarded_account_phone_number, session)
|
||||
|
||||
if quorum_count >= guarded_account.guardian_quora:
|
||||
guarded_account.reset_pin(session)
|
||||
logg.debug(f'Reset initiated for: {guarded_account.phone_number}')
|
||||
session_data['quorum_count'] = 0
|
||||
save_session_data('cic-ussd', session, session_data, ussd_session)
|
||||
|
||||
preferred_language = get_cached_preferred_language(guarded_account.blockchain_address)
|
||||
if not preferred_language:
|
||||
preferred_language = i18n.config.get('fallback')
|
||||
|
||||
notifier = Notifier()
|
||||
notifier.send_sms_notification(
|
||||
key='sms.pin_reset_initiated',
|
||||
phone_number=guarded_account.phone_number,
|
||||
preferred_language=preferred_language,
|
||||
pin_initiator=account.standard_metadata_id())
|
||||
|
||||
@@ -23,7 +23,7 @@ def is_valid_token_selection(state_machine_data: Tuple[str, dict, Account, Sessi
|
||||
account_tokens_list = session_data.get('account_tokens_list')
|
||||
if not account_tokens_list:
|
||||
wait_for_session_data('Account token list', session_data_key='account_tokens_list', ussd_session=ussd_session)
|
||||
if user_input not in ['00', '11', '22']:
|
||||
if user_input not in ['00', '22']:
|
||||
try:
|
||||
user_input = int(user_input)
|
||||
return user_input <= len(account_tokens_list)
|
||||
|
||||
@@ -32,14 +32,14 @@ celery_app = celery.current_app
|
||||
|
||||
|
||||
@celery_app.task(bind=True, base=CriticalSQLAlchemyTask)
|
||||
def account_creation_callback(self, result: str, param: str, status_code: int):
|
||||
def account_creation_callback(self, result: str, url: str, status_code: int):
|
||||
"""This function defines a task that creates a user and
|
||||
:param self: Reference providing access to the callback task instance.
|
||||
:type self: celery.Task
|
||||
:param result: The blockchain address for the created account
|
||||
:type result: str
|
||||
:param param: URL provided to callback task in cic-eth should http be used for callback.
|
||||
:type param: str
|
||||
:param url: URL provided to callback task in cic-eth should http be used for callback.
|
||||
:type url: str
|
||||
:param status_code: The status of the task to create an account
|
||||
:type status_code: int
|
||||
"""
|
||||
@@ -69,15 +69,6 @@ def account_creation_callback(self, result: str, param: str, status_code: int):
|
||||
set_active_token(blockchain_address=result, token_symbol=token_symbol)
|
||||
|
||||
queue = self.request.delivery_info.get('routing_key')
|
||||
preferences_data = {"preferred_language": param}
|
||||
# temporarily caching selected language
|
||||
key = cache_data_key(bytes.fromhex(result), MetadataPointer.PREFERENCES)
|
||||
cache_data(key, json.dumps(preferences_data))
|
||||
s_preferences_metadata = celery.signature(
|
||||
'cic_ussd.tasks.metadata.add_preferences_metadata', [result, preferences_data], queue=queue
|
||||
)
|
||||
s_preferences_metadata.apply_async()
|
||||
|
||||
s_phone_pointer = celery.signature(
|
||||
'cic_ussd.tasks.metadata.add_phone_pointer', [result, phone_number], queue=queue
|
||||
)
|
||||
|
||||
@@ -1,56 +1,9 @@
|
||||
"""
|
||||
This module is responsible for translation of ussd menu text based on a user's set preferred language.
|
||||
"""
|
||||
# standard imports
|
||||
import json
|
||||
|
||||
import i18n
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# external imports
|
||||
from cic_translations.processor import generate_translation_files, parse_csv
|
||||
from cic_types.condiments import MetadataPointer
|
||||
|
||||
# local imports
|
||||
from cic_ussd.cache import cache_data, cache_data_key
|
||||
from cic_ussd.validator import validate_presence
|
||||
|
||||
|
||||
def generate_locale_files(locale_dir: str, schema_file_path: str, translation_builder_path: str):
|
||||
""""""
|
||||
translation_builder_files = os.listdir(translation_builder_path)
|
||||
for file in translation_builder_files:
|
||||
props = Path(file)
|
||||
if props.suffix == '.csv':
|
||||
parsed_csv = parse_csv(os.path.join(translation_builder_path, file))
|
||||
generate_translation_files(
|
||||
parsed_csv=parsed_csv,
|
||||
schema_file_path=schema_file_path,
|
||||
translation_file_type=props.stem,
|
||||
translation_file_path=locale_dir
|
||||
)
|
||||
|
||||
|
||||
class Languages:
|
||||
languages_dict: dict = None
|
||||
|
||||
@classmethod
|
||||
def load_languages_dict(cls, languages_file: str):
|
||||
with open(languages_file, "r") as languages_file:
|
||||
cls.languages_dict = json.load(languages_file)
|
||||
|
||||
def cache_system_languages(self):
|
||||
system_languages: list = list(self.languages_dict.values())
|
||||
languages_list = []
|
||||
for i in range(len(system_languages)):
|
||||
language = f'{i + 1}. {system_languages[i]}'
|
||||
languages_list.append(language)
|
||||
|
||||
key = cache_data_key('system:languages'.encode('utf-8'), MetadataPointer.NONE)
|
||||
cache_data(key, json.dumps(languages_list))
|
||||
|
||||
|
||||
def translation_for(key: str, preferred_language: Optional[str] = None, **kwargs) -> str:
|
||||
"""
|
||||
|
||||
@@ -11,6 +11,3 @@ transitions=transitions/
|
||||
host =
|
||||
port =
|
||||
ssl =
|
||||
|
||||
[system]
|
||||
guardians_file = var/lib/sys/guardians.txt
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[PIP]
|
||||
extra_index_host = pip.grassrootseconomics.net
|
||||
extra_index_port =
|
||||
extra_index_port = 8433
|
||||
extra_index_path = /
|
||||
extra_index_proto = https
|
||||
|
||||
@@ -6,6 +6,3 @@ password_pepper=QYbzKff6NhiQzY3ygl2BkiKOpER8RE/Upqs/5aZWW+I=
|
||||
[machine]
|
||||
states=states/
|
||||
transitions=transitions/
|
||||
|
||||
[system]
|
||||
guardians_file = var/lib/sys/guardians.txt
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[chain]
|
||||
spec = evm:foo:1:bar
|
||||
spec = 'evm:foo:1:bar'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[PIP]
|
||||
extra_index_host = pip.grassrootseconomics.net
|
||||
extra_index_port =
|
||||
extra_index_port = 8433
|
||||
extra_index_path = /
|
||||
extra_index_proto = https
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
[locale]
|
||||
fallback=sw
|
||||
path=
|
||||
file_builders=var/lib/sys/
|
||||
|
||||
[schema]
|
||||
file_path = data/schema
|
||||
|
||||
[languages]
|
||||
file = var/lib/sys/languages.json
|
||||
path=var/lib/locale/
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
[locale]
|
||||
fallback=sw
|
||||
path=var/lib/locale/
|
||||
file_builders=var/lib/sys/
|
||||
|
||||
[schema]
|
||||
file_path = /usr/local/lib/python3.8/site-packages/cic_translations/data/schema
|
||||
|
||||
[languages]
|
||||
file = var/lib/sys/languages.json
|
||||
|
||||
@@ -4,9 +4,8 @@ billiard==3.6.4.0
|
||||
bcrypt==3.2.0
|
||||
celery==4.4.7
|
||||
cffi==1.14.6
|
||||
cic-eth~=0.12.7
|
||||
cic-eth~=0.12.6
|
||||
cic-notify~=0.4.0a12
|
||||
cic-translations~=0.0.3
|
||||
cic-types~=0.2.1a8
|
||||
confini~=0.5.2
|
||||
cic-eth-aux-erc20-demurrage-token~=0.0.3
|
||||
|
||||
@@ -13,7 +13,5 @@
|
||||
"products_edit_pin_authorization",
|
||||
"account_balances_pin_authorization",
|
||||
"account_statement_pin_authorization",
|
||||
"account_balances",
|
||||
"middle_language_set",
|
||||
"last_language_set"
|
||||
"account_balances"
|
||||
]
|
||||
@@ -2,8 +2,6 @@
|
||||
"start",
|
||||
"scan_data",
|
||||
"initial_language_selection",
|
||||
"initial_middle_language_set",
|
||||
"initial_last_language_set",
|
||||
"initial_pin_entry",
|
||||
"initial_pin_confirmation",
|
||||
"change_preferred_language"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
cic-eth[services]~=0.12.7
|
||||
Faker==11.1.0
|
||||
cic-eth[services]~=0.12.4a13
|
||||
Faker==8.1.2
|
||||
faker-e164==0.1.0
|
||||
pytest==6.2.5
|
||||
pytest-alembic==0.7.0
|
||||
pytest==6.2.4
|
||||
pytest-alembic==0.2.5
|
||||
pytest-celery==0.0.0a1
|
||||
pytest-cov==3.0.0
|
||||
pytest-mock==3.6.1
|
||||
pytest-cov==2.10.1
|
||||
pytest-mock==3.3.1
|
||||
pytest-ordering==0.6
|
||||
pytest-redis==2.3.0
|
||||
requests-mock==1.9.3
|
||||
tavern==1.18.0
|
||||
pytest-redis==2.0.0
|
||||
requests-mock==1.8.0
|
||||
tavern==1.14.2
|
||||
|
||||
@@ -2,16 +2,10 @@
|
||||
|
||||
# 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_adjusted_balance,
|
||||
get_cached_available_balance)
|
||||
from cic_ussd.account.balance import calculate_available_balance, get_balances, 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
|
||||
@@ -63,45 +57,19 @@ def test_calculate_available_balance(activated_account,
|
||||
'balance_outgoing': balance_outgoing,
|
||||
'balance_incoming': balance_incoming
|
||||
}
|
||||
assert calculate_available_balance(balances, 6) == available_balance
|
||||
assert calculate_available_balance(balances) == available_balance
|
||||
|
||||
|
||||
def test_get_cached_available_balance(activated_account,
|
||||
balances,
|
||||
cache_balances,
|
||||
cache_default_token_data,
|
||||
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)
|
||||
load_chain_spec):
|
||||
cached_available_balance = get_cached_available_balance(activated_account.blockchain_address)
|
||||
available_balance = calculate_available_balance(balances[0])
|
||||
assert cached_available_balance == available_balance
|
||||
address = blockchain_address()
|
||||
with pytest.raises(CachedDataNotFoundError) as error:
|
||||
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)
|
||||
cached_available_balance = get_cached_available_balance(address)
|
||||
assert cached_available_balance is None
|
||||
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
|
||||
assert str(error.value) == f'No cached available balance for address: {address}'
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# 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,7 +11,8 @@ from cic_ussd.account.statement import (filter_statement_transactions,
|
||||
generate,
|
||||
get_cached_statement,
|
||||
parse_statement_transactions,
|
||||
query_statement)
|
||||
query_statement,
|
||||
statement_transaction_set)
|
||||
from cic_ussd.account.transaction import transaction_actors
|
||||
from cic_ussd.cache import cache_data_key, get_cached_data
|
||||
|
||||
@@ -73,3 +74,12 @@ 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,80 +1,17 @@
|
||||
# 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 (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
|
||||
from cic_ussd.account.tokens import get_cached_default_token, get_default_token_symbol, query_default_token
|
||||
|
||||
|
||||
# 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)
|
||||
@@ -90,84 +27,6 @@ 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)
|
||||
@@ -181,38 +40,3 @@ 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,4 +1,5 @@
|
||||
# standard imports
|
||||
from decimal import Decimal
|
||||
|
||||
# external imports
|
||||
import pytest
|
||||
@@ -36,11 +37,11 @@ def test_aux_transaction_data(preferences, set_locale_files, transactions_list):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value, expected_result", [
|
||||
(50000000, 50.0),
|
||||
(100000, 0.1)
|
||||
(50000000, Decimal('50.00')),
|
||||
(100000, Decimal('0.10'))
|
||||
])
|
||||
def test_from_wei(cache_default_token_data, expected_result, value):
|
||||
assert from_wei(6, value) == expected_result
|
||||
assert from_wei(value) == expected_result
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value, expected_result", [
|
||||
@@ -48,7 +49,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(6, value) == expected_result
|
||||
assert to_wei(value) == expected_result
|
||||
|
||||
|
||||
@pytest.mark.parametrize("decimals, value, expected_result", [
|
||||
@@ -107,8 +108,8 @@ def test_outgoing_transaction_processor(activated_account,
|
||||
activated_account.blockchain_address,
|
||||
valid_recipient.blockchain_address)
|
||||
|
||||
outgoing_tx_processor.transfer(amount, 6, token_symbol)
|
||||
outgoing_tx_processor.transfer(amount, 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(6, amount)
|
||||
assert mock_transfer_api.get('value') == to_wei(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, 'en')
|
||||
create(chain_str, phone_number(), init_database)
|
||||
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=cic_type, identifier=identifier)
|
||||
metadata_client = UssdMetadataHandler(cic_type, identifier)
|
||||
assert metadata_client.cic_type == cic_type
|
||||
assert metadata_client.engine == 'pgp'
|
||||
assert metadata_client.identifier == identifier
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
# 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 os
|
||||
import datetime
|
||||
|
||||
# external imports
|
||||
from cic_types.condiments import MetadataPointer
|
||||
@@ -10,464 +10,195 @@ 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
|
||||
parse_statement_transactions,
|
||||
statement_transaction_set
|
||||
)
|
||||
from cic_ussd.account.tokens import (get_active_token_symbol,
|
||||
get_cached_token_data)
|
||||
from cic_ussd.account.tokens import get_default_token_symbol
|
||||
from cic_ussd.account.transaction import from_wei, to_wei
|
||||
from cic_ussd.cache import cache_data, cache_data_key, get_cached_data
|
||||
from cic_ussd.cache import cache_data, cache_data_key
|
||||
from cic_ussd.menu.ussd_menu import UssdMenu
|
||||
from cic_ussd.metadata import PersonMetadata
|
||||
from cic_ussd.phone_number import Support
|
||||
from cic_ussd.processor.menu import response, MenuProcessor
|
||||
from cic_ussd.processor.util import parse_person_metadata, ussd_menu_list
|
||||
from cic_ussd.processor.menu import response
|
||||
from cic_ussd.processor.util import parse_person_metadata
|
||||
from cic_ussd.translation import translation_for
|
||||
|
||||
|
||||
# test imports
|
||||
|
||||
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'
|
||||
resp = response(activated_account, with_available_balance, with_available_balance[5:], init_database,
|
||||
generic_ussd_session)
|
||||
|
||||
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()
|
||||
with_available_balance = 'ussd.kenya.account_balances.available_balance'
|
||||
with_fees = 'ussd.kenya.account_balances.with_fees'
|
||||
ussd_menu = UssdMenu.find_by_name('account_balances')
|
||||
name = ussd_menu.get('name')
|
||||
resp = response(activated_account, 'ussd.kenya.account_balances', name, init_database, generic_ussd_session)
|
||||
assert resp == translation_for(with_available_balance,
|
||||
preferred_language,
|
||||
available_balance=available_balance,
|
||||
token_symbol=token_symbol)
|
||||
|
||||
with_fees = 'ussd.account_balances.with_fees'
|
||||
key = cache_data_key(balances_identifier, MetadataPointer.BALANCES_ADJUSTED)
|
||||
identifier = bytes.fromhex(activated_account.blockchain_address)
|
||||
key = cache_data_key(identifier, MetadataPointer.BALANCES_ADJUSTED)
|
||||
adjusted_balance = 45931650.64654012
|
||||
cache_data(key, json.dumps(adjusted_balance))
|
||||
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))
|
||||
resp = response(activated_account, 'ussd.kenya.account_balances', name, init_database, generic_ussd_session)
|
||||
tax_wei = to_wei(int(available_balance)) - int(adjusted_balance)
|
||||
tax = from_wei(int(tax_wei))
|
||||
assert resp == translation_for(key=with_fees,
|
||||
preferred_language=preferred_language,
|
||||
available_balance=available_balance,
|
||||
tax=tax,
|
||||
token_symbol=token_symbol)"""
|
||||
pass
|
||||
token_symbol=token_symbol)
|
||||
|
||||
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])
|
||||
|
||||
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])
|
||||
display_key = 'ussd.kenya.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)
|
||||
|
||||
assert resp == translation_for(display_key, preferred_language, first_transaction_set=first_transaction_set)
|
||||
|
||||
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())
|
||||
display_key = 'ussd.kenya.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)
|
||||
|
||||
assert resp == translation_for(display_key, preferred_language, middle_transaction_set=middle_transaction_set)
|
||||
|
||||
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)
|
||||
display_key = 'ussd.kenya.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)
|
||||
|
||||
assert resp == translation_for(display_key, preferred_language, last_transaction_set=last_transaction_set)
|
||||
|
||||
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'
|
||||
display_key = 'ussd.kenya.display_user_metadata'
|
||||
ussd_menu = UssdMenu.find_by_name('display_user_metadata')
|
||||
name = ussd_menu.get('name')
|
||||
identifier = bytes.fromhex(activated_account.blockchain_address)
|
||||
person_metadata = PersonMetadata(identifier)
|
||||
cached_person_metadata = person_metadata.get_cached_metadata()
|
||||
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)
|
||||
resp = response(activated_account, display_key, name, init_database, generic_ussd_session)
|
||||
assert resp == parse_person_metadata(cached_person_metadata, display_key, preferred_language)
|
||||
|
||||
display_key = 'ussd.kenya.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)
|
||||
|
||||
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()
|
||||
activated_account.failed_pin_attempts = 1
|
||||
resp = response(activated_account, display_key, name, init_database, generic_ussd_session)
|
||||
retry_pin_entry = translation_for('ussd.kenya.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_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,
|
||||
display_key = 'ussd.kenya.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,
|
||||
preferred_language,
|
||||
account_balance=available_balance,
|
||||
account_token_name=token_symbol)
|
||||
|
||||
display_key = 'ussd.kenya.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())
|
||||
|
||||
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'
|
||||
display_key = 'ussd.kenya.transaction_pin_authorization'
|
||||
ussd_menu = UssdMenu.find_by_name('transaction_pin_authorization')
|
||||
name = ussd_menu.get('name')
|
||||
generic_ussd_session['data'] = {
|
||||
'recipient_phone_number': valid_recipient.phone_number,
|
||||
'transaction_amount': '15'
|
||||
}
|
||||
resp = response(activated_account, transaction_pin_authorization, transaction_pin_authorization[5:], init_database,
|
||||
generic_ussd_session)
|
||||
resp = response(activated_account, display_key, name, init_database, generic_ussd_session)
|
||||
user_input = generic_ussd_session.get('data').get('transaction_amount')
|
||||
transaction_amount = to_wei(decimals, int(user_input))
|
||||
transaction_amount = to_wei(value=int(user_input))
|
||||
tx_recipient_information = valid_recipient.standard_metadata_id()
|
||||
tx_sender_information = activated_account.standard_metadata_id()
|
||||
assert resp == translation_for(f'{transaction_pin_authorization}.first',
|
||||
assert resp == translation_for(f'{display_key}.first',
|
||||
preferred_language,
|
||||
recipient_information=tx_recipient_information,
|
||||
transaction_amount=from_wei(decimals, transaction_amount),
|
||||
transaction_amount=from_wei(transaction_amount),
|
||||
token_symbol=token_symbol,
|
||||
sender_information=tx_sender_information)
|
||||
|
||||
|
||||
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'
|
||||
display_key = 'ussd.kenya.exit_insufficient_balance'
|
||||
ussd_menu = UssdMenu.find_by_name('exit_insufficient_balance')
|
||||
name = ussd_menu.get('name')
|
||||
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(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,
|
||||
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,
|
||||
preferred_language,
|
||||
amount=from_wei(decimals, transaction_amount),
|
||||
amount=from_wei(transaction_amount),
|
||||
token_symbol=token_symbol,
|
||||
recipient_information=tx_recipient_information,
|
||||
token_balance=available_balance)
|
||||
|
||||
display_key = 'ussd.kenya.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)
|
||||
|
||||
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'
|
||||
display_key = 'ussd.kenya.exit_successful_transaction'
|
||||
ussd_menu = UssdMenu.find_by_name('exit_successful_transaction')
|
||||
name = ussd_menu.get('name')
|
||||
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(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,
|
||||
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,
|
||||
preferred_language,
|
||||
transaction_amount=from_wei(decimals, transaction_amount),
|
||||
transaction_amount=from_wei(transaction_amount),
|
||||
token_symbol=token_symbol,
|
||||
recipient_information=tx_recipient_information,
|
||||
sender_information=tx_sender_information)
|
||||
|
||||
@@ -10,16 +10,13 @@ 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.util import ussd_menu_list
|
||||
from cic_ussd.state_machine.logic.language import preferred_langauge_from_selection
|
||||
from cic_ussd.processor.ussd import get_menu, handle_menu, handle_menu_operations
|
||||
from cic_ussd.translation import translation_for
|
||||
|
||||
# test imports
|
||||
@@ -46,7 +43,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_pin_entry')
|
||||
ussd_menu = UssdMenu.find_by_name('initial_language_selection')
|
||||
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)
|
||||
@@ -78,62 +75,38 @@ def test_get_menu(activated_account,
|
||||
assert menu_resp.get('name') == ussd_menu.get('name')
|
||||
|
||||
|
||||
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,
|
||||
set_locale_files,
|
||||
task_uuid):
|
||||
initial_language_selection = 'ussd.initial_language_selection'
|
||||
def test_handle_menu_operations(activated_account,
|
||||
cache_preferences,
|
||||
celery_session_worker,
|
||||
generic_ussd_session,
|
||||
init_database,
|
||||
init_cache,
|
||||
load_chain_spec,
|
||||
load_config,
|
||||
mock_account_creation_task_result,
|
||||
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__()
|
||||
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')
|
||||
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])
|
||||
resp = handle_menu_operations(chain_str, external_session_id, phone, None, valid_service_codes[0], init_database, '4444')
|
||||
assert resp == translation_for('ussd.kenya.account_creation_prompt', preferred_language)
|
||||
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:
|
||||
@@ -144,5 +117,6 @@ def test_handle_account_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(external_session_id, phone, None, valid_service_codes[0], init_database, '1')
|
||||
assert resp == translation_for('ussd.enter_transaction_recipient', preferred_language)
|
||||
resp = handle_menu_operations(chain_str, external_session_id, phone, None, valid_service_codes[0], init_database, '1')
|
||||
assert resp == translation_for('ussd.kenya.enter_transaction_recipient', preferred_language)
|
||||
|
||||
|
||||
@@ -10,10 +10,7 @@ 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,
|
||||
ussd_menu_list)
|
||||
from cic_ussd.processor.util import latest_input, parse_person_metadata, resume_last_ussd_session
|
||||
from cic_ussd.translation import translation_for
|
||||
|
||||
|
||||
@@ -35,7 +32,7 @@ def test_parse_person_metadata(activated_account, cache_person_metadata, cache_p
|
||||
cached_person_metadata = person_metadata.get_cached_metadata()
|
||||
person_metadata = json.loads(cached_person_metadata)
|
||||
preferred_language = get_cached_preferred_language(activated_account.blockchain_address)
|
||||
display_key = 'ussd.display_person_metadata'
|
||||
display_key = 'ussd.kenya.display_person_metadata'
|
||||
parsed_person_metadata = parse_person_metadata(cached_person_metadata,
|
||||
display_key,
|
||||
preferred_language)
|
||||
@@ -63,20 +60,3 @@ 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,7 +3,8 @@ 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
|
||||
@@ -11,7 +12,9 @@ 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.state_machine.logic.account import (edit_user_metadata_attribute,
|
||||
from cic_ussd.metadata import PreferencesMetadata
|
||||
from cic_ussd.state_machine.logic.account import (change_preferred_language,
|
||||
edit_user_metadata_attribute,
|
||||
parse_gender,
|
||||
parse_person_metadata,
|
||||
save_complete_person_metadata,
|
||||
@@ -23,6 +26,32 @@ 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',
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
# 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,10 +9,7 @@ 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
|
||||
@@ -32,14 +29,8 @@ 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
|
||||
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
# 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,7 +23,6 @@ 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)
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
# 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,12 +3,13 @@ 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,
|
||||
@@ -17,6 +18,7 @@ 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
|
||||
|
||||
|
||||
@@ -47,18 +49,17 @@ def test_is_valid_transaction_amount(activated_account, amount, expected_result,
|
||||
])
|
||||
def test_has_sufficient_balance(activated_account,
|
||||
cache_balances,
|
||||
cache_token_data,
|
||||
cache_default_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_token_data,
|
||||
cache_default_token_data,
|
||||
cached_ussd_session,
|
||||
celery_session_worker,
|
||||
init_cache,
|
||||
@@ -66,12 +67,7 @@ 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)
|
||||
@@ -80,7 +76,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(decimals, 50)
|
||||
assert mock_transfer_api['value'] == to_wei(50)
|
||||
assert mock_transfer_api['token_symbol'] == load_config.get('TEST_TOKEN_SYMBOL')
|
||||
|
||||
|
||||
|
||||
@@ -6,10 +6,8 @@ def test_state_machine(activated_account_ussd_session,
|
||||
celery_session_worker,
|
||||
init_database,
|
||||
init_state_machine,
|
||||
load_languages,
|
||||
pending_account,
|
||||
set_locale_files):
|
||||
pending_account):
|
||||
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 == 'account_creation_prompt'
|
||||
assert state_machine.state == 'initial_pin_entry'
|
||||
|
||||
@@ -4,18 +4,15 @@ 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
|
||||
@@ -25,13 +22,11 @@ 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')
|
||||
@@ -53,12 +48,10 @@ 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, preferred_language, 0]
|
||||
'cic_ussd.tasks.callback_handler.account_creation_callback', [result, '', 0]
|
||||
)
|
||||
s_account_creation_callback.apply_async().get()
|
||||
account = init_database.query(Account).filter_by(phone_number=phone_number).first()
|
||||
@@ -66,7 +59,6 @@ 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')
|
||||
|
||||
@@ -125,46 +117,12 @@ 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_token_data,
|
||||
cache_default_token_data,
|
||||
cache_person_metadata,
|
||||
cache_preferences,
|
||||
celery_session_worker,
|
||||
load_chain_spec,
|
||||
mocker,
|
||||
preferences,
|
||||
@@ -199,16 +157,7 @@ def test_transaction_balances_callback(activated_account,
|
||||
mocked_chain.assert_called()
|
||||
|
||||
|
||||
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):
|
||||
def test_transaction_callback(load_chain_spec, mock_async_balance_api_query, transaction_result):
|
||||
status_code = 1
|
||||
with pytest.raises(ValueError) as error:
|
||||
s_transaction_callback = celery.signature(
|
||||
@@ -217,19 +166,13 @@ def test_transaction_callback(cache_token_data,
|
||||
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',
|
||||
[transaction_result, 'transfer', status_code])
|
||||
s_transaction_callback.apply_async().get()
|
||||
recipient_transaction, sender_transaction = transaction_actors(transaction_result)
|
||||
assert mock_async_balance_api_query.get('address') == recipient_transaction.get('blockchain_address') or sender_transaction.get('blockchain_address')
|
||||
assert mock_async_balance_api_query.get('token_symbol') == recipient_transaction.get('token_symbol') or sender_transaction.get('token_symbol')
|
||||
status_code = 0
|
||||
s_transaction_callback = celery.signature(
|
||||
'cic_ussd.tasks.callback_handler.transaction_callback',
|
||||
[transaction_result, 'transfer', status_code])
|
||||
s_transaction_callback.apply_async().get()
|
||||
recipient_transaction, sender_transaction = transaction_actors(transaction_result)
|
||||
assert mock_async_balance_api_query.get('address') == recipient_transaction.get('blockchain_address') or sender_transaction.get('blockchain_address')
|
||||
assert mock_async_balance_api_query.get('token_symbol') == recipient_transaction.get('token_symbol') or sender_transaction.get('token_symbol')
|
||||
|
||||
|
||||
|
||||
@@ -14,14 +14,13 @@ 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(6, notification_data.get('token_value'))
|
||||
amount = from_wei(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,11 +52,6 @@ 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]
|
||||
)
|
||||
|
||||
@@ -8,8 +8,8 @@ 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.kenya.exit", "en", "+254712345678", "END Thank you for using the service."),
|
||||
("ussd.kenya.exit", "sw", "+254712345678", "END Asante kwa kutumia huduma.")
|
||||
])
|
||||
def test_send_sms_notification(celery_session_worker,
|
||||
expected_message,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user