Compare commits

...

20 Commits

Author SHA1 Message Date
1f8559dddd using the new base image with contract migration 2021-06-22 13:36:45 -07:00
Spencer Ofwiti
e5b1352970 Merge branch 'spencer/meta-cicd' into 'master'
Refactor meta ci-cd pipeline.

See merge request grassrootseconomics/cic-internal-integration!171
2021-06-07 16:11:03 +00:00
Spencer Ofwiti
89b90da5d2 Refactor meta ci-cd pipeline. 2021-06-07 16:11:03 +00:00
9607994c31 Merge branch 'philip/add-support-phone-number' into 'master'
Philip/add support phone number

See merge request grassrootseconomics/cic-internal-integration!175
2021-06-07 08:02:03 +00:00
0da617d29e Philip/add support phone number 2021-06-07 08:02:03 +00:00
56bcad16a5 Merge branch 'philip/refactor-metadata-entry' into 'master'
Philip/refactor signup steps.

See merge request grassrootseconomics/cic-internal-integration!173
2021-06-07 07:47:03 +00:00
77d9936e39 Philip/refactor signup steps. 2021-06-07 07:47:02 +00:00
Louis Holbrook
72aeefc78b Merge branch 'lash/simple-compose' into 'master'
Simplify docker compose setup

See merge request grassrootseconomics/cic-internal-integration!180
2021-06-04 20:10:16 +00:00
nolash
fab9b0c520 Add long timeout to first account create in contract migration part 2 2021-06-04 22:00:06 +02:00
9566f8c8e2 Merge branch 'philip/qfix-vbumps' into 'master'
Bumps cic-eth and cic-base versions.

See merge request grassrootseconomics/cic-internal-integration!179
2021-06-04 08:34:14 +00:00
007d7a5121 Bumps cic-eth and cic-base versions. 2021-06-04 11:12:04 +03:00
fc20849aff Merge branch 'bvander/contract-migration-requirements' into 'master'
pull the req out of the container and bump em

See merge request grassrootseconomics/cic-internal-integration!178
2021-06-03 18:54:19 +00:00
1605e53216 pull the req out of the container and bump em 2021-06-03 11:43:13 -07:00
200fdf0e3c Merge branch 'no-host-mount-startup' into 'master'
no local file mounts for config files

See merge request grassrootseconomics/cic-internal-integration!177
2021-06-03 17:22:49 +00:00
022db04198 no local file mounts for config files 2021-06-03 17:22:47 +00:00
1c17048981 Update README.md 2021-06-03 17:19:55 +00:00
Louis Holbrook
04c0963f33 Merge branch 'lash/tmp-check-chaintool' into 'master'
Update queue/syncer module structure

See merge request grassrootseconomics/cic-internal-integration!176
2021-06-03 13:51:55 +00:00
Louis Holbrook
096ed9bc27 Update queue/syncer module structure 2021-06-03 13:51:55 +00:00
1a931eced4 Merge branch 'philip/management-integration-tests' into 'master'
Philip/management integration tests

See merge request grassrootseconomics/cic-internal-integration!159
2021-06-03 13:40:51 +00:00
ed9e032890 Philip/management integration tests 2021-06-03 13:40:51 +00:00
81 changed files with 6365 additions and 247 deletions

3
.gitignore vendored
View File

@@ -8,3 +8,6 @@ gmon.out
*.egg-info
dist/
build/
**/*sqlite
**/.nyc_output
**/coverage

View File

@@ -31,7 +31,7 @@ from chainqueue.db.enum import (
status_str,
)
from chainqueue.error import TxStateChangeError
from chainqueue.query import get_tx
from chainqueue.sql.query import get_tx
from eth_erc20 import ERC20
# local imports

View File

@@ -19,7 +19,7 @@ from cic_eth_registry import CICRegistry
from cic_eth_registry.erc20 import ERC20Token
from chainqueue.db.models.otx import Otx
from chainqueue.db.enum import StatusEnum
from chainqueue.query import get_tx_cache
from chainqueue.sql.query import get_tx_cache
from eth_erc20 import ERC20
# local imports

View File

@@ -5,7 +5,7 @@ import datetime
import celery
from chainlib.chain import ChainSpec
from chainlib.eth.tx import unpack
import chainqueue.query
import chainqueue.sql.query
from chainqueue.db.enum import (
StatusEnum,
is_alive,
@@ -28,7 +28,7 @@ celery_app = celery.current_app
def get_tx_cache(chain_spec_dict, tx_hash):
chain_spec = ChainSpec.from_dict(chain_spec_dict)
session = SessionBase.create_session()
r = chainqueue.query.get_tx_cache(chain_spec, tx_hash, session=session)
r = chainqueue.sql.query.get_tx_cache(chain_spec, tx_hash, session=session)
session.close()
return r
@@ -37,7 +37,7 @@ def get_tx_cache(chain_spec_dict, tx_hash):
def get_tx(chain_spec_dict, tx_hash):
chain_spec = ChainSpec.from_dict(chain_spec_dict)
session = SessionBase.create_session()
r = chainqueue.query.get_tx(chain_spec, tx_hash, session=session)
r = chainqueue.sql.query.get_tx(chain_spec, tx_hash, session=session)
session.close()
return r
@@ -46,7 +46,7 @@ def get_tx(chain_spec_dict, tx_hash):
def get_account_tx(chain_spec_dict, address, as_sender=True, as_recipient=True, counterpart=None):
chain_spec = ChainSpec.from_dict(chain_spec_dict)
session = SessionBase.create_session()
r = chainqueue.query.get_account_tx(chain_spec, address, as_sender=True, as_recipient=True, counterpart=None, session=session)
r = chainqueue.sql.query.get_account_tx(chain_spec, address, as_sender=True, as_recipient=True, counterpart=None, session=session)
session.close()
return r
@@ -55,17 +55,17 @@ def get_account_tx(chain_spec_dict, address, as_sender=True, as_recipient=True,
def get_upcoming_tx_nolock(chain_spec_dict, status=StatusEnum.READYSEND, not_status=None, recipient=None, before=None, limit=0, session=None):
chain_spec = ChainSpec.from_dict(chain_spec_dict)
session = SessionBase.create_session()
r = chainqueue.query.get_upcoming_tx(chain_spec, status, not_status=not_status, recipient=recipient, before=before, limit=limit, session=session, decoder=unpack)
r = chainqueue.sql.query.get_upcoming_tx(chain_spec, status, not_status=not_status, recipient=recipient, before=before, limit=limit, session=session, decoder=unpack)
session.close()
return r
def get_status_tx(chain_spec, status, not_status=None, before=None, exact=False, limit=0, session=None):
return chainqueue.query.get_status_tx_cache(chain_spec, status, not_status=not_status, before=before, exact=exact, limit=limit, session=session, decoder=unpack)
return chainqueue.sql.query.get_status_tx_cache(chain_spec, status, not_status=not_status, before=before, exact=exact, limit=limit, session=session, decoder=unpack)
def get_paused_tx(chain_spec, status=None, sender=None, session=None, decoder=None):
return chainqueue.query.get_paused_tx_cache(chain_spec, status=status, sender=sender, session=session, decoder=unpack)
return chainqueue.sql.query.get_paused_tx_cache(chain_spec, status=status, sender=sender, session=session, decoder=unpack)
def get_nonce_tx(chain_spec, nonce, sender):

View File

@@ -1,6 +1,6 @@
# external imports
from chainlib.chain import ChainSpec
import chainqueue.state
import chainqueue.sql.state
# local imports
import celery
@@ -14,7 +14,7 @@ celery_app = celery.current_app
def set_sent(chain_spec_dict, tx_hash, fail=False):
chain_spec = ChainSpec.from_dict(chain_spec_dict)
session = SessionBase.create_session()
r = chainqueue.state.set_sent(chain_spec, tx_hash, fail, session=session)
r = chainqueue.sql.state.set_sent(chain_spec, tx_hash, fail, session=session)
session.close()
return r
@@ -23,7 +23,7 @@ def set_sent(chain_spec_dict, tx_hash, fail=False):
def set_final(chain_spec_dict, tx_hash, block=None, tx_index=None, fail=False):
chain_spec = ChainSpec.from_dict(chain_spec_dict)
session = SessionBase.create_session()
r = chainqueue.state.set_final(chain_spec, tx_hash, block=block, tx_index=tx_index, fail=fail, session=session)
r = chainqueue.sql.state.set_final(chain_spec, tx_hash, block=block, tx_index=tx_index, fail=fail, session=session)
session.close()
return r
@@ -32,7 +32,7 @@ def set_final(chain_spec_dict, tx_hash, block=None, tx_index=None, fail=False):
def set_cancel(chain_spec_dict, tx_hash, manual=False):
chain_spec = ChainSpec.from_dict(chain_spec_dict)
session = SessionBase.create_session()
r = chainqueue.state.set_cancel(chain_spec, tx_hash, manual, session=session)
r = chainqueue.sql.state.set_cancel(chain_spec, tx_hash, manual, session=session)
session.close()
return r
@@ -41,7 +41,7 @@ def set_cancel(chain_spec_dict, tx_hash, manual=False):
def set_rejected(chain_spec_dict, tx_hash):
chain_spec = ChainSpec.from_dict(chain_spec_dict)
session = SessionBase.create_session()
r = chainqueue.state.set_rejected(chain_spec, tx_hash, session=session)
r = chainqueue.sql.state.set_rejected(chain_spec, tx_hash, session=session)
session.close()
return r
@@ -50,7 +50,7 @@ def set_rejected(chain_spec_dict, tx_hash):
def set_fubar(chain_spec_dict, tx_hash):
chain_spec = ChainSpec.from_dict(chain_spec_dict)
session = SessionBase.create_session()
r = chainqueue.state.set_fubar(chain_spec, tx_hash, session=session)
r = chainqueue.sql.state.set_fubar(chain_spec, tx_hash, session=session)
session.close()
return r
@@ -59,7 +59,7 @@ def set_fubar(chain_spec_dict, tx_hash):
def set_manual(chain_spec_dict, tx_hash):
chain_spec = ChainSpec.from_dict(chain_spec_dict)
session = SessionBase.create_session()
r = chainqueue.state.set_manual(chain_spec, tx_hash, session=session)
r = chainqueue.sql.state.set_manual(chain_spec, tx_hash, session=session)
session.close()
return r
@@ -68,7 +68,7 @@ def set_manual(chain_spec_dict, tx_hash):
def set_ready(chain_spec_dict, tx_hash):
chain_spec = ChainSpec.from_dict(chain_spec_dict)
session = SessionBase.create_session()
r = chainqueue.state.set_ready(chain_spec, tx_hash, session=session)
r = chainqueue.sql.state.set_ready(chain_spec, tx_hash, session=session)
session.close()
return r
@@ -77,7 +77,7 @@ def set_ready(chain_spec_dict, tx_hash):
def set_reserved(chain_spec_dict, tx_hash):
chain_spec = ChainSpec.from_dict(chain_spec_dict)
session = SessionBase.create_session()
r = chainqueue.state.set_reserved(chain_spec, tx_hash, session=session)
r = chainqueue.sql.state.set_reserved(chain_spec, tx_hash, session=session)
session.close()
return r
@@ -86,7 +86,7 @@ def set_reserved(chain_spec_dict, tx_hash):
def set_waitforgas(chain_spec_dict, tx_hash):
chain_spec = ChainSpec.from_dict(chain_spec_dict)
session = SessionBase.create_session()
r = chainqueue.state.set_waitforgas(chain_spec, tx_hash, session=session)
r = chainqueue.sql.state.set_waitforgas(chain_spec, tx_hash, session=session)
session.close()
return r
@@ -95,7 +95,7 @@ def set_waitforgas(chain_spec_dict, tx_hash):
def get_state_log(chain_spec_dict, tx_hash):
chain_spec = ChainSpec.from_dict(chain_spec_dict)
session = SessionBase.create_session()
r = chainqueue.state.get_state_log(chain_spec, tx_hash, session=session)
r = chainqueue.sql.state.get_state_log(chain_spec, tx_hash, session=session)
session.close()
return r
@@ -104,6 +104,6 @@ def get_state_log(chain_spec_dict, tx_hash):
def obsolete(chain_spec_dict, tx_hash, final):
chain_spec = ChainSpec.from_dict(chain_spec_dict)
session = SessionBase.create_session()
r = chainqueue.state.obsolete_by_cache(chain_spec, tx_hash, final, session=session)
r = chainqueue.sql.state.obsolete_by_cache(chain_spec, tx_hash, final, session=session)
session.close()
return r

View File

@@ -15,14 +15,14 @@ from sqlalchemy import tuple_
from sqlalchemy import func
from chainlib.chain import ChainSpec
from chainlib.eth.tx import unpack
import chainqueue.state
import chainqueue.sql.state
from chainqueue.db.enum import (
StatusEnum,
StatusBits,
is_alive,
dead,
)
from chainqueue.tx import create
from chainqueue.sql.tx import create
from chainqueue.error import NotLocalTxError
from chainqueue.db.enum import status_str

View File

@@ -21,7 +21,7 @@ from chainqueue.db.enum import (
StatusBits,
)
from chainqueue.error import NotLocalTxError
from chainqueue.state import set_reserved
from chainqueue.sql.state import set_reserved
# local imports
import cic_eth

View File

@@ -10,7 +10,7 @@ from chainlib.eth.tx import unpack
from chainqueue.db.enum import StatusBits
from chainqueue.db.models.tx import TxCache
from chainqueue.db.models.otx import Otx
from chainqueue.query import get_paused_tx_cache as get_paused_tx
from chainqueue.sql.query import get_paused_tx_cache as get_paused_tx
# local imports
from cic_eth.db.models.base import SessionBase

View File

@@ -3,7 +3,7 @@ import logging
# external imports
import celery
from chainqueue.state import obsolete_by_cache
from chainqueue.sql.state import obsolete_by_cache
logg = logging.getLogger()

View File

@@ -10,7 +10,7 @@ version = (
0,
11,
0,
'beta.15',
'beta.16',
)
version_object = semver.VersionInfo(

View File

@@ -1,4 +1,4 @@
cic-base~=0.1.2b14
cic-base~=0.1.2b15
celery==4.4.7
crypto-dev-signer~=0.4.14b3
confini~=0.3.6rc3
@@ -16,8 +16,8 @@ moolb~=0.1.1b2
eth-address-index~=0.1.1a11
chainlib~=0.0.3rc2
hexathon~=0.0.1a7
chainsyncer[sql]~=0.0.2a4
chainqueue~=0.0.2b1
chainsyncer[sql]~=0.0.2a5
chainqueue~=0.0.2b3
sarafu-faucet==0.0.3a3
erc20-faucet==0.2.1a4
coincurve==15.0.0

View File

@@ -1,7 +1,7 @@
# external imports
from chainlib.connection import RPCConnection
from chainlib.eth.nonce import OverrideNonceOracle
from chainqueue.tx import create as queue_create
from chainqueue.sql.tx import create as queue_create
from chainlib.eth.tx import (
TxFormat,
unpack,
@@ -16,7 +16,7 @@ from chainlib.eth.block import (
block_by_number,
Block,
)
from chainqueue.state import (
from chainqueue.sql.state import (
set_waitforgas,
)
from hexathon import strip_0x

View File

@@ -15,7 +15,7 @@ from chainlib.eth.block import (
)
from erc20_faucet import Faucet
from hexathon import strip_0x
from chainqueue.query import get_account_tx
from chainqueue.sql.query import get_account_tx
# local imports
from cic_eth.runnable.daemons.filters.register import RegistrationFilter

View File

@@ -17,8 +17,8 @@ from chainlib.eth.block import (
)
from chainqueue.db.models.otx import Otx
from chainqueue.db.enum import StatusBits
from chainqueue.tx import create as queue_create
from chainqueue.state import (
from chainqueue.sql.tx import create as queue_create
from chainqueue.sql.state import (
set_reserved,
set_ready,
set_sent,

View File

@@ -15,7 +15,7 @@ from chainlib.eth.block import (
Block,
)
from hexathon import strip_0x
from chainqueue.query import get_account_tx
from chainqueue.sql.query import get_account_tx
# local imports
from cic_eth.runnable.daemons.filters.transferauth import TransferAuthFilter

View File

@@ -17,8 +17,8 @@ from chainlib.eth.block import (
)
from chainqueue.db.models.otx import Otx
from chainqueue.db.enum import StatusBits
from chainqueue.tx import create as queue_create
from chainqueue.state import (
from chainqueue.sql.tx import create as queue_create
from chainqueue.sql.state import (
set_reserved,
set_ready,
set_sent,

View File

@@ -29,12 +29,12 @@ from chainqueue.db.enum import (
StatusBits,
status_str,
)
from chainqueue.state import (
from chainqueue.sql.state import (
set_fubar,
set_ready,
set_reserved,
)
from chainqueue.query import (
from chainqueue.sql.query import (
get_tx,
get_nonce_tx_cache,
)

View File

@@ -11,7 +11,7 @@ from chainlib.eth.nonce import (
OverrideNonceOracle,
RPCNonceOracle,
)
from chainqueue.tx import create as queue_create
from chainqueue.sql.tx import create as queue_create
from chainlib.eth.tx import (
raw,
receipt,
@@ -23,14 +23,14 @@ from chainlib.eth.gas import (
Gas,
OverrideGasOracle,
)
from chainqueue.state import (
from chainqueue.sql.state import (
set_reserved,
set_sent,
set_ready,
)
from chainqueue.db.models.otx import Otx
from chainqueue.db.enum import StatusBits
from chainqueue.query import get_nonce_tx_cache
from chainqueue.sql.query import get_nonce_tx_cache
from eth_erc20 import ERC20
from cic_eth_registry import CICRegistry

View File

@@ -12,7 +12,7 @@ from eth_accounts_index import AccountsIndex
from chainlib.eth.tx import (
transaction,
)
from chainqueue.state import (
from chainqueue.sql.state import (
set_reserved,
)

View File

@@ -5,7 +5,7 @@ import logging
import celery
from chainlib.connection import RPCConnection
from chainlib.eth.nonce import OverrideNonceOracle
from chainqueue.tx import (
from chainqueue.sql.tx import (
create as queue_create,
)
from chainlib.eth.gas import (
@@ -13,7 +13,7 @@ from chainlib.eth.gas import (
OverrideGasOracle,
)
from chainlib.eth.tx import TxFormat
from chainqueue.query import get_nonce_tx_cache
from chainqueue.sql.query import get_nonce_tx_cache
from chainqueue.db.models.otx import Otx
from chainqueue.db.enum import StatusBits
from hexathon import add_0x

View File

@@ -21,10 +21,10 @@ from chainlib.eth.constant import (
MINIMUM_FEE_UNITS,
MINIMUM_FEE_PRICE,
)
from chainqueue.tx import create as queue_create
from chainqueue.query import get_tx
from chainqueue.sql.tx import create as queue_create
from chainqueue.sql.query import get_tx
from chainqueue.db.enum import StatusBits
from chainqueue.state import (
from chainqueue.sql.state import (
set_ready,
set_reserved,
set_sent,

View File

@@ -18,8 +18,8 @@ from chainlib.eth.tx import (
)
from hexathon import strip_0x
from chainqueue.db.models.otx import Otx
from chainqueue.tx import create as queue_create
from chainqueue.state import (
from chainqueue.sql.tx import create as queue_create
from chainqueue.sql.state import (
set_reserved,
set_ready,
set_sent,

View File

@@ -5,7 +5,7 @@ import logging
# external imports
import pytest
import celery
from chainqueue.tx import create as queue_create
from chainqueue.sql.tx import create as queue_create
from chainlib.eth.nonce import (
RPCNonceOracle,
OverrideNonceOracle,
@@ -23,7 +23,7 @@ from hexathon import (
add_0x,
strip_0x,
)
from chainqueue.state import (
from chainqueue.sql.state import (
set_reserved,
set_ready,
)

View File

@@ -3,3 +3,5 @@ dist
dist-web
dist-server
scratch
coverage
.nyc_output

View File

@@ -3,17 +3,38 @@
variables:
APP_NAME: cic-meta
DOCKERFILE_PATH: $APP_NAME/docker/Dockerfile
IMAGE_TAG: $CI_REGISTRY_IMAGE/$APP_NAME:unittest-$CI_COMMIT_SHORT_SHA
.cic_meta_changes_target:
rules:
- changes:
- $CONTEXT/$APP_NAME/*
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# - changes:
# - $CONTEXT/$APP_NAME/*
- when: always
build-mr-cic-meta:
cic-meta-build-mr:
stage: build
extends:
- .cic_meta_variables
- .cic_meta_changes_target
script:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > "/kaniko/.docker/config.json"
# - /kaniko/executor --context $CONTEXT --dockerfile $DOCKERFILE_PATH $KANIKO_CACHE_ARGS --destination $IMAGE_TAG
- /kaniko/executor --context $CONTEXT --dockerfile $DOCKERFILE_PATH $KANIKO_CACHE_ARGS --destination $IMAGE_TAG
test-mr-cic-meta:
extends:
- .cic_meta_changes_target
- .py_build_merge_request
- .cic_meta_variables
- .cic_meta_changes_target
stage: test
image: $IMAGE_TAG
script:
- cd /tmp/src/cic-meta
- npm install --dev
- npm run test
- npm run test:coverage
needs: ["cic-meta-build-mr"]
build-push-cic-meta:
extends:

View File

@@ -4,29 +4,28 @@ WORKDIR /tmp/src/cic-meta
RUN apk add --no-cache postgresql bash
COPY cic-meta/package.json \
./
# required to build the cic-client-meta module
COPY cic-meta/src/ src/
COPY cic-meta/tests/ tests/
COPY cic-meta/scripts/ scripts/
# copy the dependencies
COPY cic-meta/package.json .
COPY cic-meta/tsconfig.json .
COPY cic-meta/webpack.config.js .
RUN npm install
# see exports_dir gpg.ini
COPY cic-meta/tests/ tests/
COPY cic-meta/tests/*.asc /root/pgp/
RUN alias tsc=node_modules/typescript/bin/tsc
# copy runtime configs
COPY cic-meta/.config/ /usr/local/etc/cic-meta/
# COPY cic-meta/scripts/server/initdb/server.postgres.sql /usr/local/share/cic-meta/sql/server.sql
# db migrations
COPY cic-meta/docker/db.sh ./db.sh
RUN chmod 755 ./db.sh
#RUN alias ts-node=/tmp/src/cic-meta/node_modules/ts-node/dist/bin.js
#ENTRYPOINT [ "./node_modules/ts-node/dist/bin.js", "./scripts/server/server.ts" ]
RUN alias tsc=node_modules/typescript/bin/tsc
COPY cic-meta/docker/start_server.sh ./start_server.sh
RUN chmod 755 ./start_server.sh
ENTRYPOINT ["sh", "./start_server.sh"]

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@
"preferGlobal": true,
"scripts": {
"test": "mocha -r node_modules/node-localstorage/register -r ts-node/register tests/*.ts",
"test:coverage": "nyc mocha tests/*.ts --timeout 3000 --check-coverage=true",
"build": "node_modules/typescript/bin/tsc -d --outDir dist src/index.ts",
"build-server": "tsc -d --outDir dist-server scripts/server/*.ts",
"pack": "node_modules/typescript/bin/tsc -d --outDir dist && webpack",
@@ -34,7 +35,9 @@
"devDependencies": {
"@types/mocha": "^8.0.3",
"mocha": "^8.2.0",
"nock": "^13.1.0",
"node-localstorage": "^2.1.6",
"nyc": "^15.1.0",
"ts-node": "^9.0.0",
"typescript": "^4.0.5",
"webpack": "^5.4.0",
@@ -50,5 +53,26 @@
"license": "GPL-3.0-or-later",
"engines": {
"node": ">=14.16.1"
},
"nyc": {
"include": [
"src/**/*.ts"
],
"extension": [
".ts"
],
"require": [
"ts-node/register"
],
"reporter": [
"text",
"html"
],
"sourceMap": true,
"instrument": true,
"branches": ">80",
"lines": ">80",
"functions": ">80",
"statements": ">80"
}
}

View File

@@ -31,7 +31,7 @@ function handleNoMergeGet(db, digest, keystore) {
doh(e);
});
}).catch((e) => {
console.error('mesage', e);
console.error('message', e);
doh(e);
});
})
@@ -46,7 +46,7 @@ function handleServerMergePost(data, db, digest, keystore, signer) {
let e = undefined;
let s = undefined;
if (v === undefined) {
s = new Syncable(digest, data);
s = new Syncable(digest, o);
s.onwrap = (e) => {
whohoo(e.toJSON());
};

View File

@@ -204,7 +204,7 @@ async function processRequest(req, res) {
}
if (content === undefined) {
console.error('empty onctent', data);
console.error('empty content', data);
res.writeHead(400, {"Content-Type": "text/plain"});
res.end();
return;

View File

@@ -9,7 +9,7 @@ class Custom extends Syncable implements Addressable {
super('', v);
Custom.toKey(name).then((cid) => {
this.id = cid;
this.value = v;
this.name = name;
});
}

View File

@@ -100,13 +100,15 @@ class Meta {
identifier = await User.toKey(name);
} else if (type === 'phone') {
identifier = await Phone.toKey(name);
} else {
} else if (type === 'custom') {
identifier = await Custom.toKey(name);
} else {
identifier = await Custom.toKey(name, type);
}
return identifier;
}
private wrap(syncable: Syncable): Promise<Envelope> {
wrap(syncable: Syncable): Promise<Envelope> {
return new Promise<Envelope>(async (resolve, reject) => {
syncable.setSigner(this.signer);
syncable.onwrap = async (env) => {

View File

@@ -0,0 +1,49 @@
import * as assert from 'assert';
import {Custom} from "../src";
const testName = 'areas';
const testObject = {
area: ['Nairobi', 'Mombasa', 'Kilifi']
}
const testNameKey = '8f3da0c90ba2b89ff217da96f6088cbaf987a1b58bc33c3a5e526e53cec7cfed';
const testIdentifier = ':cic.area'
const testIdentifierKey = 'da6194e6f33726546e82c328df4c120b844d6427859156518bd600765bf8b2b7';
describe('custom', () => {
context('with predefined data', () => {
it('should create a custom object', () => {
const custom = new Custom(testName, testObject);
setTimeout(() => {
assert.strictEqual(custom.name, testName);
assert.deepStrictEqual(custom.m.data, testObject);
assert.strictEqual(custom.key(), testNameKey)
}, 0);
});
});
context('without predefined data', () => {
it('should create a custom object', () => {
const custom = new Custom(testName);
setTimeout(() => {
assert.strictEqual(custom.name, testName);
assert.deepStrictEqual(custom.m.data, {});
assert.strictEqual(custom.key(), testNameKey)
}, 0);
});
});
describe('#toKey()', () => {
context('without a custom identifier', () => {
it('should generate a key from the custom name', async () => {
assert.strictEqual(await Custom.toKey(testName), testNameKey);
});
});
context('with a custom identifier', () => {
it('should generate a key from the custom name with a custom identifier', async () => {
assert.strictEqual(await Custom.toKey(testName, testIdentifier), testIdentifierKey);
});
});
});
});

176
apps/cic-meta/tests/meta.ts Normal file
View File

@@ -0,0 +1,176 @@
import * as assert from 'assert';
import * as fs from 'fs';
const nock = require('nock');
import {Meta} from "../src";
import {getResponse, metaData, networkErrorResponse, notFoundResponse, putResponse} from "./response";
import {Syncable} from "@cicnet/crdt-meta";
const metaUrl = 'https://meta.dev.grassrootseconomics.net';
const testAddress = '0xc1912fee45d61c87cc5ea59dae31190fffff232d';
const testAddressKey = 'a51472cb4df63b199a4de01335b1b4d1bbee27ff4f03340aa1d592f26c6acfe2';
const testPhone = '+254123456789';
const testPhoneKey = 'be3cc8212b7eb57c6217ddd42230bd8ccd2f01382bf8c1c77d3a683fa5a9bb16';
const testName = 'areas'
const testNameKey = '8f3da0c90ba2b89ff217da96f6088cbaf987a1b58bc33c3a5e526e53cec7cfed';
const testIdentifier = ':cic.area'
const testIdentifierKey = 'da6194e6f33726546e82c328df4c120b844d6427859156518bd600765bf8b2b7';
function readFile(filename) {
if(!fs.existsSync(filename)) {
console.error(`File ${filename} not found`);
return;
}
return fs.readFileSync(filename, {encoding: 'utf8', flag: 'r'});
}
const privateKey = readFile('./privatekeys.asc');
describe('meta', () => {
beforeEach(() => {
nock(metaUrl)
.get(`/${testAddressKey}`)
.reply(200, getResponse);
nock(metaUrl)
.get(`/${testPhoneKey}`)
.reply(200, getResponse);
nock(metaUrl)
.get(`/${testAddress}`)
.reply(404);
nock(metaUrl)
.get(`/${testIdentifier}`)
.replyWithError(networkErrorResponse);
nock(metaUrl)
.put(`/${testAddressKey}`)
.reply(200, putResponse);
nock(metaUrl)
.put(`/${testAddress}`)
.reply(404);
nock(metaUrl)
.post('/post')
.reply(500);
});
describe('#get()', () => {
it('should fetch data from the meta service', async () => {
const account = await Meta.get(testAddressKey, metaUrl);
assert.strictEqual(account.toJSON(account), getResponse.payload);
});
context('if item is not found', () => {
it('should respond with an error', async () => {
const account = await Meta.get(testAddress, metaUrl);
assert.strictEqual(account, `404: Not Found`);
});
});
context('in case of network error', () => {
it('should respond with an error', async () => {
const account = await Meta.get(testIdentifier, metaUrl);
assert.strictEqual(account, `Request to ${metaUrl}/${testIdentifier} failed. Connection error.`);
});
});
})
describe('#set()', () => {
context('object data', () => {
it('should set data to the meta server', () => {
const meta = new Meta(metaUrl, privateKey);
meta.onload = async (status) => {
const response = await meta.set(testAddressKey, metaData);
assert.strictEqual(response, `${putResponse.status}: ${putResponse.statusText}`);
}
});
});
context('string data', () => {
it('should set data to the meta server', () => {
const meta = new Meta(metaUrl, privateKey);
meta.onload = async (status) => {
const response = await meta.set(testPhoneKey, testAddress);
assert.strictEqual(response, `${putResponse.status}: ${putResponse.statusText}`);
}
});
});
context('in case of network error', () => {
it('should respond with an error', () => {
const meta = new Meta(metaUrl, privateKey);
meta.onload = async (status) => {
const response = await meta.set(testIdentifier, metaData);
assert.strictEqual(response, `Request to ${metaUrl}/${testIdentifier} failed. Connection error.`);
}
});
});
});
describe('#updateMeta()', () => {
it('should update data in the meta server', async () => {
const syncable = new Syncable(testAddressKey, metaData);
const meta = new Meta(metaUrl, privateKey);
meta.onload = async (status) => {
const response = await meta.updateMeta(syncable, testAddressKey);
assert.strictEqual(response, putResponse);
}
});
context('if item is not found', () => {
it('should respond with an error', () => {
const syncable = new Syncable(testAddress, metaData);
const meta = new Meta(metaUrl, privateKey);
meta.onload = async (status) => {
const response = await meta.updateMeta(syncable, testAddress);
assert.strictEqual(response, notFoundResponse);
}
});
});
});
describe('#wrap()', () => {
it('should sign a syncable object', function () {
const syncable = new Syncable(testAddressKey, metaData);
const meta = new Meta(metaUrl, privateKey);
meta.onload = async (status) => {
const response = await meta.wrap(syncable);
assert.strictEqual(response.toJSON(), getResponse);
}
});
})
describe('#getIdentifier()', () => {
context('without type', () => {
it('should return an identifier', async () => {
assert.strictEqual(await Meta.getIdentifier(testName), testNameKey);
});
});
context('with user type', () => {
it('should return an identifier', async () => {
assert.strictEqual(await Meta.getIdentifier(testAddress, 'user'), testAddressKey);
});
});
context('with phone type', () => {
it('should return an identifier', async () => {
assert.strictEqual(await Meta.getIdentifier(testPhone, 'phone'), testPhoneKey);
});
});
context('with custom type', () => {
it('should return an identifier', async () => {
assert.strictEqual(await Meta.getIdentifier(testName, 'custom'), testNameKey);
});
});
context('with unrecognised type', () => {
it('should return an identifier', async () => {
assert.strictEqual(await Meta.getIdentifier(testName, testIdentifier), testIdentifierKey);
});
});
});
});

View File

@@ -0,0 +1,24 @@
import * as assert from 'assert';
import {Phone} from "../src";
const testAddress = '0xc1912fee45d61c87cc5ea59dae31190fffff232d';
const testPhone = '+254123456789';
const testPhoneKey = 'be3cc8212b7eb57c6217ddd42230bd8ccd2f01382bf8c1c77d3a683fa5a9bb16';
describe('phone', () => {
it('should create a phone object', () => {
const phone = new Phone(testAddress, testPhone);
setTimeout(() => {
assert.strictEqual(phone.address, testAddress);
assert.strictEqual(phone.m.data.msisdn, testPhone);
assert.strictEqual(phone.key(), testPhoneKey)
}, 0);
});
describe('#toKey()', () => {
it('should generate a key from the phone number', async () => {
assert.strictEqual(await Phone.toKey(testPhone), testPhoneKey);
});
});
});

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,54 @@
import * as assert from 'assert';
import { User } from "../src";
const testAddress = '0xc1912fee45d61c87cc5ea59dae31190fffff232d';
const testAddressKey = 'a51472cb4df63b199a4de01335b1b4d1bbee27ff4f03340aa1d592f26c6acfe2';
const testUser = {
user: {
firstName: 'Test',
lastName: 'User'
}
}
describe('user', () => {
context('without predefined data', () => {
it('should create a user object', () => {
const user = new User(testAddress);
setTimeout(() => {
assert.strictEqual(user.address, testAddress);
assert.strictEqual(user.key(), testAddressKey);
assert.strictEqual(user.m.data.user.firstName, '');
assert.strictEqual(user.m.data.user.lastName, '');
}, 0);
});
});
context('with predefined data', () => {
it('should create a user object', () => {
const user = new User(testAddress, testUser);
setTimeout(() => {
assert.strictEqual(user.address, testAddress);
assert.strictEqual(user.key(), testAddressKey);
assert.strictEqual(user.m.data.user.firstName, testUser.user.firstName);
assert.strictEqual(user.m.data.user.lastName, testUser.user.lastName);
}, 0);
});
});
describe('#setName()', () => {
it('should set user\'s names to metadata', () => {
const user = new User(testAddress);
user.setName(testUser.user.firstName, testUser.user.lastName);
assert.strictEqual(user.m.data.user.firstName, testUser.user.firstName);
assert.strictEqual(user.m.data.user.lastName, testUser.user.lastName);
});
});
describe('#toKey()', () => {
it('should generate a key from the user\'s address', async () => {
assert.strictEqual(await User.toKey(testAddress), testAddressKey);
});
});
});

View File

@@ -2,7 +2,7 @@
"compilerOptions": {
"baseUrl": ".",
"outDir": "./dist.browser",
"target": "es5",
"target": "es2015",
"module": "commonjs",
"moduleResolution": "node",
"lib": ["es2016", "dom", "es5"],

View File

@@ -56,7 +56,7 @@ class AfricasTalkingNotifier:
response = self.api_client.send(message=message, recipients=[recipient])
logg.debug(f'africastalking response no-sender-id {response}')
recipients = response.get('Recipients')
recipients = response.get('SMSMessageData').get('Recipients')
if len(recipients) != 1:
status = response.get('SMSMessageData').get('Message')

View File

@@ -28,6 +28,7 @@ packages =
cic_notify
cic_notify.db
cic_notify.db.models
cic_notify.ext
cic_notify.tasks.sms
cic_notify.runnable
scripts =

View File

@@ -5,6 +5,7 @@ LOCALE_PATH=/usr/src/cic-ussd/var/lib/locale/
MAX_BODY_LENGTH=1024
PASSWORD_PEPPER=QYbzKff6NhiQzY3ygl2BkiKOpER8RE/Upqs/5aZWW+I=
SERVICE_CODE=*483*46#,*483*061#,*384*96#
SUPPORT_PHONE_NUMBER=0757628885
[phone_number]
REGION=KE

View File

@@ -5,6 +5,7 @@ LOCALE_PATH=var/lib/locale/
MAX_BODY_LENGTH=1024
PASSWORD_PEPPER=QYbzKff6NhiQzY3ygl2BkiKOpER8RE/Upqs/5aZWW+I=
SERVICE_CODE=*483*46#
SUPPORT_PHONE_NUMBER=0757628885
[ussd]
MENU_FILE=/usr/local/lib/python3.8/site-packages/cic_ussd/db/ussd_menu.json

View File

@@ -0,0 +1,4 @@
[test]
gift_value = 50.00
server_url = http://localhost:63315/
token_symbol = GFT

View File

@@ -238,13 +238,43 @@
"description": "Menu to display a user's entire profile",
"display_key": "ussd.kenya.display_user_metadata",
"name": "display_user_metadata",
"parent": "account_management"
"parent": "metadata_management"
},
"41": {
"description": "The recipient is not in the system",
"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.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.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.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.kenya.products_edit_pin_authorization",
"name": "products_edit_pin_authorization",
"parent": "metadata_management"
},
"46": {
"description": "Pin confirmation for pin change.",
"display_key": "ussd.kenya.new_pin_confirmation",
"name": "new_pin_confirmation",
"parent": "metadata_management"
}
}

View File

@@ -78,28 +78,27 @@ class MetadataRequestsHandler(Metadata):
:param data: The data to be stored in the metadata server.
:type data: dict|str
"""
data = json.dumps(data).encode('utf-8')
data = json.dumps(data)
result = make_request(method='POST', url=self.url, data=data, headers=self.headers)
metadata_http_error_handler(result=result)
metadata = result.content
metadata = result.json()
self.edit(data=metadata)
def edit(self, data: bytes):
def edit(self, data: Union[Dict, str]):
""" This function is responsible for editing data in the metadata server corresponding to a unique pointer.
:param data: The data to be edited in the metadata server.
:type data: bytes
:type data: dict
"""
cic_meta_signer = Signer()
signature = cic_meta_signer.sign_digest(data=data)
algorithm = cic_meta_signer.get_operational_key().get('algo')
decoded_data = data.decode('utf-8')
formatted_data = {
'm': data.decode('utf-8'),
'm': json.dumps(data),
's': {
'engine': self.engine,
'algo': algorithm,
'data': signature,
'digest': json.loads(data).get('digest'),
'digest': data.get('digest'),
}
}
formatted_data = json.dumps(formatted_data)
@@ -110,19 +109,32 @@ class MetadataRequestsHandler(Metadata):
decoded_identifier = self.identifier.decode("utf-8")
except UnicodeDecodeError:
decoded_identifier = self.identifier.hex()
logg.info(f'identifier: {decoded_identifier}. metadata pointer: {self.metadata_pointer} set to: {decoded_data}.')
logg.info(f'identifier: {decoded_identifier}. metadata pointer: {self.metadata_pointer} set to: {data}.')
def query(self):
"""This function is responsible for querying the metadata server for data corresponding to a unique pointer."""
"""
:return:
:rtype:
"""
# retrieve the metadata
result = make_request(method='GET', url=self.url)
metadata_http_error_handler(result=result)
response_data = result.json()
data = json.loads(response_data)
if not isinstance(data, dict):
raise ValueError(f'Invalid data object: {data}.')
# json serialize retrieved data
result_data = result.json()
# validate result data format
if not isinstance(result_data, dict):
raise ValueError(f'Invalid result data object: {result_data}.')
if result.status_code == 200 and self.cic_type == ':cic.person':
# validate person metadata
person = Person()
deserialized_person = person.deserialize(person_data=data)
data = json.dumps(deserialized_person.serialize())
cache_data(self.metadata_pointer, data=data)
person_data = person.deserialize(person_data=result_data)
# format new person data for caching
data = json.dumps(person_data.serialize())
# cache metadata
cache_data(key=self.metadata_pointer, data=data)
logg.debug(f'caching: {data} with key: {self.metadata_pointer}')

View File

@@ -47,14 +47,13 @@ class Signer:
logg.debug(f'using signing key: {key_id}, algorithm: {key_algorithm}')
return gpg_keys[0]
def sign_digest(self, data: bytes):
def sign_digest(self, data: dict):
"""
:param data:
:type data:
:return:
:rtype:
"""
data = json.loads(data)
digest = data['digest']
key_id = self.get_operational_key().get('keyid')
signature = self.gpg.sign(digest, passphrase=self.gpg_passphrase, keyid=key_id)

View File

@@ -41,3 +41,7 @@ def get_user_by_phone_number(phone_number: str) -> Optional[Account]:
phone_number = process_phone_number(phone_number=phone_number, region='KE')
user = Account.session.query(Account).filter_by(phone_number=phone_number).first()
return user
class Support:
phone_number = None

View File

@@ -19,7 +19,7 @@ from cic_ussd.db.models.ussd_session import UssdSession
from cic_ussd.error import MetadataNotFoundError, SeppukuError
from cic_ussd.menu.ussd_menu import UssdMenu
from cic_ussd.metadata import blockchain_address_to_metadata_pointer
from cic_ussd.phone_number import get_user_by_phone_number
from cic_ussd.phone_number import get_user_by_phone_number, Support
from cic_ussd.redis import cache_data, create_cached_data_key, get_cached_data
from cic_ussd.state_machine import UssdStateMachine
from cic_ussd.conversions import to_wei, from_wei
@@ -251,9 +251,9 @@ def process_display_user_metadata(user: Account, display_key: str):
identifier=blockchain_address_to_metadata_pointer(blockchain_address=user.blockchain_address),
cic_type=':cic.person'
)
user_metadata = get_cached_data(key)
if user_metadata:
user_metadata = json.loads(user_metadata)
cached_metadata = get_cached_data(key)
if cached_metadata:
user_metadata = json.loads(cached_metadata)
contact_data = get_contact_data_from_vcard(vcard=user_metadata.get('vcard'))
logg.debug(f'{contact_data}')
full_name = f'{contact_data.get("given")} {contact_data.get("family")}'
@@ -270,7 +270,24 @@ def process_display_user_metadata(user: Account, display_key: str):
products=products
)
else:
raise MetadataNotFoundError(f'Expected person metadata but found none in cache for key: {key}')
# TODO [Philip]: All these translations could be moved to translation files.
logg.warning(f'Expected person metadata but found none in cache for key: {key}')
absent = ''
if user.preferred_language == 'en':
absent = 'Not provided'
elif user.preferred_language == 'sw':
absent = 'Haijawekwa'
return translation_for(
key=display_key,
preferred_language=user.preferred_language,
full_name=absent,
gender=absent,
location=absent,
products=absent
)
def process_account_statement(user: Account, display_key: str, ussd_session: dict):
@@ -433,7 +450,8 @@ def process_request(user_input: str, user: Account, ussd_session: Optional[dict]
'exit_invalid_pin',
'exit_invalid_new_pin',
'exit_pin_mismatch',
'exit_invalid_request'
'exit_invalid_request',
'exit_successful_transaction'
] and person_metadata is not None:
return UssdMenu.find_by_name(name='start')
else:
@@ -466,6 +484,14 @@ def next_state(ussd_session: dict, user: Account, user_input: str) -> str:
return new_state
def process_exit_invalid_menu_option(display_key: str, preferred_language: str):
return translation_for(
key=display_key,
preferred_language=preferred_language,
support_phone=Support.phone_number
)
def custom_display_text(
display_key: str,
menu_name: str,
@@ -502,5 +528,7 @@ def custom_display_text(
return process_account_statement(display_key=display_key, user=user, ussd_session=ussd_session)
elif menu_name == 'display_user_metadata':
return process_display_user_metadata(display_key=display_key, user=user)
elif menu_name == 'exit_invalid_menu_option':
return process_exit_invalid_menu_option(display_key=display_key, preferred_language=user.preferred_language)
else:
return translation_for(key=display_key, preferred_language=user.preferred_language)

View File

@@ -26,7 +26,7 @@ from cic_ussd.metadata.base import Metadata
from cic_ussd.operations import (define_response_with_content,
process_menu_interaction_requests,
define_multilingual_responses)
from cic_ussd.phone_number import process_phone_number
from cic_ussd.phone_number import process_phone_number, Support
from cic_ussd.processor import get_default_token_data
from cic_ussd.redis import cache_data, create_cached_data_key, InMemoryStore
from cic_ussd.requests import (get_request_endpoint,
@@ -126,6 +126,8 @@ else:
valid_service_codes = config.get('APP_SERVICE_CODE').split(",")
Support.phone_number = config.get('APP_SUPPORT_PHONE_NUMBER')
def application(env, start_response):
"""Loads python code for application to be accessible over web server

View File

@@ -13,7 +13,7 @@ import bcrypt
# local imports
from cic_ussd.db.models.account import AccountStatus, Account
from cic_ussd.encoder import PasswordEncoder, create_password_hash
from cic_ussd.encoder import PasswordEncoder, create_password_hash, check_password_hash
from cic_ussd.operations import persist_session_to_db_task, create_or_update_session
from cic_ussd.redis import InMemoryStore
@@ -78,9 +78,13 @@ def save_initial_pin_to_session_data(state_machine_data: Tuple[str, dict, Accoun
# set initial pin data
initial_pin = create_password_hash(user_input)
session_data = {
'initial_pin': initial_pin
}
if ussd_session.get('session_data'):
session_data = ussd_session.get('session_data')
session_data['initial_pin'] = initial_pin
else:
session_data = {
'initial_pin': initial_pin
}
# create new in memory ussd session with current ussd session data
create_or_update_session(
@@ -103,9 +107,8 @@ def pins_match(state_machine_data: Tuple[str, dict, Account]) -> bool:
"""
user_input, ussd_session, user = state_machine_data
initial_pin = ussd_session.get('session_data').get('initial_pin')
fernet = PasswordEncoder(PasswordEncoder.key)
initial_pin = fernet.decrypt(initial_pin.encode())
return bcrypt.checkpw(user_input.encode(), initial_pin)
logg.debug(f'USSD SESSION: {ussd_session}')
return check_password_hash(user_input, initial_pin)
def complete_pin_change(state_machine_data: Tuple[str, dict, Account]):

View File

@@ -11,7 +11,7 @@ from cic_ussd.balance import BalanceManager, compute_operational_balance
from cic_ussd.chain import Chain
from cic_ussd.db.models.account import AccountStatus, Account
from cic_ussd.operations import save_to_in_memory_ussd_session_data
from cic_ussd.phone_number import get_user_by_phone_number
from cic_ussd.phone_number import get_user_by_phone_number, process_phone_number
from cic_ussd.processor import retrieve_token_symbol
from cic_ussd.redis import create_cached_data_key, get_cached_data
from cic_ussd.transactions import OutgoingTransactionProcessor
@@ -30,7 +30,7 @@ def is_valid_recipient(state_machine_data: Tuple[str, dict, Account]) -> bool:
"""
user_input, ussd_session, user = state_machine_data
recipient = get_user_by_phone_number(phone_number=user_input)
is_not_initiator = user_input != user.phone_number
is_not_initiator = process_phone_number(user_input, 'KE') != user.phone_number
has_active_account_status = user.get_account_status() == AccountStatus.ACTIVE.name
return is_not_initiator and has_active_account_status and recipient is not None

View File

@@ -64,13 +64,17 @@ def process_gender_user_input(user: Account, user_input: str):
if user.preferred_language == 'en':
if user_input == '1':
gender = 'Male'
else:
elif user_input == '2':
gender = 'Female'
elif user_input == '3':
gender = 'Other'
else:
if user_input == '1':
gender = 'Mwanaume'
else:
elif user_input == '2':
gender = 'Mwanamke'
elif user_input == '3':
gender = 'Nyingine'
return gender
@@ -88,14 +92,18 @@ def save_metadata_attribute_to_session_data(state_machine_data: Tuple[str, dict,
key = ''
if 'given_name' in current_state:
key = 'given_name'
elif 'family_name' in current_state:
if 'family_name' in current_state:
key = 'family_name'
elif 'gender' in current_state:
if 'gender' in current_state:
key = 'gender'
user_input = process_gender_user_input(user=user, user_input=user_input)
elif 'location' in current_state:
if 'location' in current_state:
key = 'location'
elif 'products' in current_state:
if 'products' in current_state:
key = 'products'
# check if there is existing session data
@@ -121,12 +129,20 @@ def format_user_metadata(metadata: dict, user: Account):
gender = metadata.get('gender')
given_name = metadata.get('given_name')
family_name = metadata.get('family_name')
location = {
"area_name": metadata.get('location')
}
products = []
if metadata.get('products'):
# check whether there's existing location data
if isinstance(metadata.get('location'), dict):
location = metadata.get('location')
else:
location = {
"area_name": metadata.get('location')
}
# check whether it is a list
if isinstance(metadata.get('products'), list):
products = metadata.get('products')
else:
products = metadata.get('products').split(',')
phone_number = user.phone_number
date_registered = int(user.created.replace().timestamp())
blockchain_address = user.blockchain_address
@@ -192,28 +208,27 @@ def edit_user_metadata_attribute(state_machine_data: Tuple[str, dict, Account]):
# validate user metadata
person = Person()
user_metadata = json.loads(user_metadata)
deserialized_person = person.deserialize(person_data=user_metadata)
# edit specific metadata attribute
if given_name:
deserialized_person.given_name = given_name
elif family_name:
deserialized_person.family_name = family_name
elif gender:
deserialized_person.gender = gender
elif location:
user_metadata['given_name'] = given_name
if family_name:
user_metadata['family_name'] = family_name
if gender:
user_metadata['gender'] = gender
if location:
# get existing location metadata:
location_data = user_metadata.get('location')
location_data['area_name'] = location
deserialized_person.location = location_data
elif products:
deserialized_person.products = products
user_metadata['location'] = location_data
if products:
user_metadata['products'] = products
edited_metadata = deserialized_person.serialize()
user_metadata = format_user_metadata(metadata=user_metadata, user=user)
s_edit_person_metadata = celery.signature(
'cic_ussd.tasks.metadata.edit_person_metadata',
[blockchain_address, edited_metadata]
'cic_ussd.tasks.metadata.create_person_metadata',
[blockchain_address, user_metadata]
)
s_edit_person_metadata.apply_async(queue='cic-ussd')

View File

@@ -19,4 +19,6 @@ def translation_for(key: str, preferred_language: Optional[str] = None, **kwargs
"""
if preferred_language:
i18n.set('locale', preferred_language)
else:
i18n.set('locale', i18n.config.get('fallback'))
return i18n.t(key, **kwargs)

View File

@@ -1,4 +1,4 @@
cic_base[full_graph]~=0.1.2b2
cic-eth~=0.11.0b9
cic-notify~=0.4.0a4
cic_base[full_graph]~=0.1.2b15
cic-eth~=0.11.0b16
cic-notify~=0.4.0a5
cic-types~=0.1.0a10

View File

@@ -6,7 +6,10 @@
"enter_new_pin",
"new_pin_confirmation",
"display_user_metadata",
"standard_pin_authorization",
"name_edit_pin_authorization",
"gender_edit_pin_authorization",
"location_edit_pin_authorization",
"products_edit_pin_authorization",
"account_balances_pin_authorization",
"account_statement_pin_authorization",
"account_balances"

View File

@@ -1,7 +1,11 @@
pytest==6.0.1
Faker==8.1.2
faker-e164==0.1.0
pytest==6.2.4
pytest-alembic==0.2.5
pytest-celery==0.0.0a1
pytest-cov==2.10.1
pytest-mock==3.3.1
pytest-ordering==0.6
pytest-redis==2.0.0
requests-mock==1.8.0
requests-mock==1.8.0
tavern==1.14.2

View File

@@ -6,6 +6,7 @@ from cic_types.pytest import *
from tests.fixtures.config import *
from tests.fixtures.db import *
from tests.fixtures.celery import *
from tests.fixtures.integration import *
from tests.fixtures.user import *
from tests.fixtures.ussd_session import *
from tests.fixtures.redis import *

View File

@@ -0,0 +1,249 @@
# standard imports
# external imports
import pytest
from faker import Faker
# local imports
# test imports
from tests.helpers.accounts import phone_number, pin_number, session_id
fake = Faker()
@pytest.fixture(scope='function')
def generate_phone_number() -> str:
return phone_number()
@pytest.fixture(scope='function')
def generate_session_id() -> str:
return session_id()
@pytest.fixture(scope='session')
def first_account_phone_number() -> str:
return phone_number()
@pytest.fixture(scope='session')
def second_account_phone_number() -> str:
return phone_number()
@pytest.fixture(scope='session')
def first_account_pin_number() -> str:
return pin_number()
@pytest.fixture(scope='session')
def second_account_pin_number() -> str:
return pin_number()
@pytest.fixture(scope='session')
def first_metadata_entry_session_id() -> str:
return session_id()
@pytest.fixture(scope='session')
def second_metadata_entry_session_id() -> str:
return session_id()
@pytest.fixture(scope='session')
def first_transaction_session_id() -> str:
return session_id()
@pytest.fixture(scope='session')
def second_transaction_session_id() -> str:
return session_id()
@pytest.fixture(scope='session')
def first_account_given_name() -> str:
return fake.first_name()
@pytest.fixture(scope='session')
def second_account_given_name() -> str:
return fake.first_name()
@pytest.fixture(scope='session')
def first_account_family_name() -> str:
return fake.last_name()
@pytest.fixture(scope='session')
def second_account_family_name() -> str:
return fake.last_name()
@pytest.fixture(scope='session')
def first_account_location() -> str:
return fake.city()
@pytest.fixture(scope='session')
def second_account_location() -> str:
return fake.city()
@pytest.fixture(scope='session')
def first_account_product() -> str:
return fake.color_name()
@pytest.fixture(scope='session')
def second_account_product() -> str:
return fake.color_name()
@pytest.fixture(scope='session')
def first_account_verify_balance_session_id() -> str:
return session_id()
@pytest.fixture(scope='session')
def second_account_verify_balance_session_id() -> str:
return session_id()
@pytest.fixture(scope='session')
def first_profile_management_session_id() -> str:
return session_id()
@pytest.fixture(scope='session')
def second_profile_management_session_id() -> str:
return session_id()
@pytest.fixture(scope='session')
def first_account_change_given_name() -> str:
return fake.first_name()
@pytest.fixture(scope='session')
def second_account_change_given_name() -> str:
return fake.first_name()
@pytest.fixture(scope='session')
def first_account_change_family_name() -> str:
return fake.last_name()
@pytest.fixture(scope='session')
def second_account_change_family_name() -> str:
return fake.last_name()
@pytest.fixture(scope='session')
def first_account_change_location() -> str:
return fake.city()
@pytest.fixture(scope='session')
def second_account_change_location() -> str:
return fake.city()
@pytest.fixture(scope='session')
def first_account_change_product() -> str:
return fake.color_name()
@pytest.fixture(scope='session')
def second_account_change_product() -> str:
return fake.color_name()
@pytest.fixture(scope='session')
def first_profile_management_session_id_1() -> str:
return session_id()
@pytest.fixture(scope='session')
def second_profile_management_session_id_1() -> str:
return session_id()
@pytest.fixture(scope='session')
def first_profile_management_session_id_2() -> str:
return session_id()
@pytest.fixture(scope='session')
def second_profile_management_session_id_2() -> str:
return session_id()
@pytest.fixture(scope='session')
def first_profile_management_session_id_3() -> str:
return session_id()
@pytest.fixture(scope='session')
def second_profile_management_session_id_3() -> str:
return session_id()
@pytest.fixture(scope='session')
def first_profile_management_session_id_4() -> str:
return session_id()
@pytest.fixture(scope='session')
def second_profile_management_session_id_4() -> str:
return session_id()
@pytest.fixture(scope='session')
def first_account_management_session_id() -> str:
return session_id()
@pytest.fixture(scope='session')
def second_account_management_session_id() -> str:
return session_id()
@pytest.fixture(scope='session')
def first_account_management_session_id_1() -> str:
return session_id()
@pytest.fixture(scope='session')
def second_account_management_session_id_1() -> str:
return session_id()
@pytest.fixture(scope='session')
def first_account_new_pin_number() -> str:
return pin_number()
@pytest.fixture(scope='session')
def second_account_new_pin_number() -> str:
return pin_number()
@pytest.fixture(scope='session')
def gift_value(load_config):
return load_config.get('TEST_GIFT_VALUE')
@pytest.fixture(scope='session')
def server_url(load_config):
return load_config.get('TEST_SERVER_URL')
@pytest.fixture(scope='session')
def token_symbol(load_config):
return load_config.get('TEST_TOKEN_SYMBOL')

View File

@@ -0,0 +1,26 @@
# standard imports
import random
import uuid
# 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')
def session_id() -> str:
return uuid.uuid4().hex
def pin_number() -> int:
return random.randint(1000, 9999)

View File

@@ -0,0 +1,11 @@
import logging
logg = logging.getLogger()
logg.setLevel(logging.DEBUG)
def validate_response(response, expected_response):
"""Makes sure that the response received matches the expected response"""
logg.debug(f'RESPONSE: {response.content.decode("utf-8")}')
assert response.content.decode('utf-8') == expected_response

View File

@@ -0,0 +1,2 @@
#!/bin/bash
PYTHONPATH=. py.test --debug -vv --log-level debug -s --log-cli-level debug

View File

@@ -0,0 +1,466 @@
test_name: Test the creation of accounts through the cic_user_ussd_server entrypoint.
marks:
- usefixtures:
- gift_value
- server_url
- token_symbol
- generate_session_id
- first_account_phone_number
- second_account_phone_number
- first_account_pin_number
- second_account_pin_number
- first_account_family_name
- second_account_family_name
- first_account_given_name
- second_account_given_name
- first_account_location
- second_account_location
- first_account_product
- second_account_product
- first_metadata_entry_session_id
- second_metadata_entry_session_id
- first
stages:
- name: Initiate account creation process [first account].
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{generate_session_id}"
phoneNumber: "{first_account_phone_number}"
text: ""
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '175'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "END Your account is being created. You will receive an SMS when your account is ready.\nAkaunti yako ya Sarafu inatayarishwa. Utapokea ujumbe wa SMS akaunti yako ikiwa tayari.\n"
- name: Initiate account creation process [second account].
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{generate_session_id}"
phoneNumber: "{second_account_phone_number}"
text: ""
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '175'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "END Your account is being created. You will receive an SMS when your account is ready.\nAkaunti yako ya Sarafu inatayarishwa. Utapokea ujumbe wa SMS akaunti yako ikiwa tayari.\n"
delay_after: 5
- name: Initaite account metadata entry [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_metadata_entry_session_id}"
phoneNumber: "{first_account_phone_number}"
text: ""
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '61'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Welcome to Sarafu Network\n1. English\n2. Kiswahili\n3. Help"
- name: Initaite account metadata entry [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_metadata_entry_session_id}"
phoneNumber: "{second_account_phone_number}"
text: ""
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '61'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Welcome to Sarafu Network\n1. English\n2. Kiswahili\n3. Help"
- name: Select preferred language [English]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_metadata_entry_session_id}"
phoneNumber: "{first_account_phone_number}"
text: "1"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '64'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Please enter a new four number PIN for your account.\n0. Back"
- name: Select preferred language [Kiswahili]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_metadata_entry_session_id}"
phoneNumber: "{second_account_phone_number}"
text: "2"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '71'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Tafadhali weka pin mpya yenye nambari nne kwa akaunti yako\n0. Nyuma"
- name: Enter pin number [{first_account_pin_number} - first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_metadata_entry_session_id}"
phoneNumber: "{first_account_phone_number}"
text: "1*{first_account_pin_number}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '44'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Enter your four number PIN again\n0. Back"
- name: Enter pin number [second_account_pin_number - second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_metadata_entry_session_id}"
phoneNumber: "{second_account_phone_number}"
text: "2*{second_account_pin_number}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '31'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Weka PIN yako tena\n0. Nyuma"
- name: Pin number confirmation [first_account_pin_number - first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_metadata_entry_session_id}"
phoneNumber: "{first_account_phone_number}"
text: "1*{first_account_pin_number}*{first_account_pin_number}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '28'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Enter first name\n0. Back"
- name: Pin number confirmation [{second_account_pin_number} - second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_metadata_entry_session_id}"
phoneNumber: "{second_account_phone_number}"
text: "2*{second_account_pin_number}*{second_account_pin_number}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '37'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Weka jina lako la kwanza\n0. Nyuma"
- name: Enter first name [first_account_given_name - first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_metadata_entry_session_id}"
phoneNumber: "{first_account_phone_number}"
text: "1*{first_account_pin_number}*{first_account_pin_number}*{first_account_given_name}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '29'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Enter family name\n0. Back"
- name: Enter first name [second_account_given_name - second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_metadata_entry_session_id}"
phoneNumber: "{second_account_phone_number}"
text: "2*{second_account_pin_number}*{second_account_pin_number}*{second_account_given_name}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '37'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Weka jina lako la mwisho\n0. Nyuma"
- name: Enter last name [first_account_family_name - first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_metadata_entry_session_id}"
phoneNumber: "{first_account_phone_number}"
text: "1*{first_account_pin_number}*{first_account_pin_number}*{first_account_given_name}*{first_account_family_name}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '51'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Enter gender\n1. Male\n2. Female\n3. Other\n0. Back"
- name: Enter last name [second_account_family_name - second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_metadata_entry_session_id}"
phoneNumber: "{second_account_phone_number}"
text: "2*{second_account_pin_number}*{second_account_pin_number}*{second_account_given_name}*{second_account_family_name}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '64'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Weka jinsia yako\n1. Mwanaume\n2. Mwanamke\n3. Nyngine\n0. Nyuma"
- name: Select gender [Male - first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_metadata_entry_session_id}"
phoneNumber: "{first_account_phone_number}"
text: "1*{first_account_pin_number}*{first_account_pin_number}*{first_account_given_name}*{first_account_family_name}*1"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '31'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Enter your location\n0. Back"
- name: Select gender [Female - second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_metadata_entry_session_id}"
phoneNumber: "{second_account_phone_number}"
text: "2*{second_account_pin_number}*{second_account_pin_number}*{second_account_given_name}*{second_account_family_name}*2"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '27'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Weka eneo lako\n0. Nyuma"
- name: Enter location [first_account_location - first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_metadata_entry_session_id}"
phoneNumber: "{first_account_phone_number}"
text: "1*{first_account_pin_number}*{first_account_pin_number}*{first_account_given_name}*{first_account_family_name}*1*{first_account_location}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '55'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Please enter a product or service you offer\n0. Back"
- name: Enter location [second_account_location - second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_metadata_entry_session_id}"
phoneNumber: "{second_account_phone_number}"
text: "2*{second_account_pin_number}*{second_account_pin_number}*{second_account_given_name}*{second_account_family_name}*2*{second_account_location}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '42'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Weka bidhaa ama huduma unauza\n0. Nyuma"
- name: Enter product [first_account_product - first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_metadata_entry_session_id}"
phoneNumber: "{first_account_phone_number}"
text: "1*{first_account_pin_number}*{first_account_pin_number}*{first_account_given_name}*{first_account_family_name}*1*{first_account_location}*{first_account_product}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '51'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Balance {gift_value} {token_symbol}\n1. Send\n2. My Account\n3. Help"
delay_before: 10
- name: Enter product [second_account_product - second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_metadata_entry_session_id}"
phoneNumber: "{second_account_phone_number}"
text: "2*{second_account_pin_number}*{second_account_pin_number}*{second_account_given_name}*{second_account_family_name}*2*{second_account_location}*{second_account_product}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '56'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Salio {gift_value} {token_symbol}\n1. Tuma\n2. Akaunti yangu\n3. Usaidizi"
delay_before: 10

View File

@@ -0,0 +1,587 @@
test_name: Test performing account management operations.
marks:
- usefixtures:
- server_url
- token_symbol
- first_account_pin_number
- second_account_pin_number
- first_account_phone_number
- second_account_phone_number
- first_account_management_session_id
- second_account_management_session_id
- first_account_management_session_id_1
- second_account_management_session_id_1
- first_account_new_pin_number
- second_account_new_pin_number
- fourth
stages:
- name: Account management start menu [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_account_management_session_id}"
phoneNumber: "{first_account_phone_number}"
text: ""
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '51'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Balance 58.00 {token_symbol}\n1. Send\n2. My Account\n3. Help"
- name: Account management start menu [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_account_management_session_id}"
phoneNumber: "{second_account_phone_number}"
text: ""
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '56'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Salio 42.00 {token_symbol}\n1. Tuma\n2. Akaunti yangu\n3. Usaidizi"
- name: Account management menu [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_account_management_session_id}"
phoneNumber: "{first_account_phone_number}"
text: "2"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '105'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON My account\n1. My profile\n2. Change language\n3. Check balance\n4. Check statement\n5. Change PIN\n0. Back"
- name: Account management menu [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_account_management_session_id}"
phoneNumber: "{second_account_phone_number}"
text: "2"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '148'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Akaunti yangu\n1. Wasifu wangu\n2. Chagua lugha utakayotumia\n3. Angalia salio\n4. Angalia taarifa ya matumizi\n5. Badilisha nambari ya siri\n0. Nyuma"
- name: Language change [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_account_management_session_id}"
phoneNumber: "{first_account_phone_number}"
text: "2*2"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '51'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Choose language\n1. English\n2. Kiswahili\n0. Back"
- name: Language change [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_account_management_session_id}"
phoneNumber: "{second_account_phone_number}"
text: "2*2"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '51'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Chagua lugha\n1. Kingereza\n2. Kiswahili\n0. Nyuma"
- name: Select language [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_account_management_session_id}"
phoneNumber: "{first_account_phone_number}"
text: "2*2*2"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '30'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "END Asante kwa kutumia huduma."
- name: Select language [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_account_management_session_id}"
phoneNumber: "{second_account_phone_number}"
text: "2*2*1"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '36'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "END Thank you for using the service."
- name: Second account management start menu [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_account_management_session_id_1}"
phoneNumber: "{first_account_phone_number}"
text: ""
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '56'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Salio 58.00 {token_symbol}\n1. Tuma\n2. Akaunti yangu\n3. Usaidizi"
- name: Second account management start menu [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_account_management_session_id_1}"
phoneNumber: "{second_account_phone_number}"
text: ""
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '51'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Balance 42.00 {token_symbol}\n1. Send\n2. My Account\n3. Help"
- name: Second account management menu [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_account_management_session_id_1}"
phoneNumber: "{first_account_phone_number}"
text: "2"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '148'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Akaunti yangu\n1. Wasifu wangu\n2. Chagua lugha utakayotumia\n3. Angalia salio\n4. Angalia taarifa ya matumizi\n5. Badilisha nambari ya siri\n0. Nyuma"
- name: Second account management menu [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_account_management_session_id_1}"
phoneNumber: "{second_account_phone_number}"
text: "2"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '105'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON My account\n1. My profile\n2. Change language\n3. Check balance\n4. Check statement\n5. Change PIN\n0. Back"
- name: Check balance [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_account_management_session_id_1}"
phoneNumber: "{first_account_phone_number}"
text: "2*3"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '49'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Tafadhali weka PIN yako kuona salio.\n0. Nyuma"
- name: Check balance [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_account_management_session_id_1}"
phoneNumber: "{second_account_phone_number}"
text: "2*3"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '50'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Please enter your PIN to view balances\n0. Back"
- name: Display balances [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_account_management_session_id_1}"
phoneNumber: "{first_account_phone_number}"
text: "2*3*{first_account_pin_number}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Salio zako ni zifuatazo:\n salio: 58.00 {token_symbol}\n ushuru: {token_symbol}\n tuzo: {token_symbol}\n0. Nyuma"
- name: Display balances [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_account_management_session_id_1}"
phoneNumber: "{second_account_phone_number}"
text: "2*3*{second_account_pin_number}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Your balances are as follows:\n balance: 42.00 {token_symbol}\n fees: {token_symbol}\n rewards: {token_symbol}\n0. Back"
- name: Resume account management menu [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_account_management_session_id_1}"
phoneNumber: "{first_account_phone_number}"
text: "2*3*{first_account_pin_number}*0"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '148'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Akaunti yangu\n1. Wasifu wangu\n2. Chagua lugha utakayotumia\n3. Angalia salio\n4. Angalia taarifa ya matumizi\n5. Badilisha nambari ya siri\n0. Nyuma"
- name: Resume account management menu [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_account_management_session_id_1}"
phoneNumber: "{second_account_phone_number}"
text: "2*3*{second_account_pin_number}*0"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '105'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON My account\n1. My profile\n2. Change language\n3. Check balance\n4. Check statement\n5. Change PIN\n0. Back"
- name: Change pin number [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_account_management_session_id_1}"
phoneNumber: "{first_account_phone_number}"
text: "2*3*{first_account_pin_number}*0*5"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '34'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Weka nambari ya siri.\n0. Nyuma"
- name: Change pin number [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_account_management_session_id_1}"
phoneNumber: "{second_account_phone_number}"
text: "2*3*{second_account_pin_number}*0*5"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '30'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Enter current PIN.\n0. Back"
- name: Enter old pin [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_account_management_session_id_1}"
phoneNumber: "{first_account_phone_number}"
text: "2*3*{first_account_pin_number}*0*5*{first_account_pin_number}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '38'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Weka nambari ya siri mpya\n0. Nyuma"
- name: Enter old pin [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_account_management_session_id_1}"
phoneNumber: "{second_account_phone_number}"
text: "2*3*{second_account_pin_number}*0*5*{second_account_pin_number}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '42'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Enter your new four number PIN\n0. Back"
- name: Enter new pin [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_account_management_session_id_1}"
phoneNumber: "{first_account_phone_number}"
text: "2*3*{first_account_pin_number}*0*5*{first_account_pin_number}*{first_account_new_pin_number}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '31'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Weka PIN yako tena\n0. Nyuma"
- name: Enter new pin [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_account_management_session_id_1}"
phoneNumber: "{second_account_phone_number}"
text: "2*3*{second_account_pin_number}*0*5*{second_account_pin_number}*{second_account_new_pin_number}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '48'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Enter your new four number PIN again\n0. Back"
- name: Enter new pin confirmation [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_account_management_session_id_1}"
phoneNumber: "{first_account_phone_number}"
text: "2*3*{first_account_pin_number}*0*5*{first_account_pin_number}*{first_account_new_pin_number}*{first_account_new_pin_number}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '91'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Ombi lako limetumwa. Utapokea uthibitishaji wa SMS kwa muda mfupi.\n00. Nyuma\n99. Ondoka"
- name: Enter new pin confirmation [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_account_management_session_id_1}"
phoneNumber: "{second_account_phone_number}"
text: "2*3*{second_account_pin_number}*0*5*{second_account_pin_number}*{second_account_new_pin_number}*{second_account_new_pin_number}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '82'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Your request has been sent. You will receive an SMS shortly.\n00. Back\n99. Exit"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,282 @@
test_name: Test that the two test accounts can trade with each other.
marks:
- usefixtures:
- gift_value
- server_url
- token_symbol
- first_account_family_name
- second_account_family_name
- first_account_given_name
- second_account_given_name
- first_account_phone_number
- second_account_phone_number
- first_account_pin_number
- second_account_pin_number
- first_transaction_session_id
- second_transaction_session_id
- first_account_verify_balance_session_id
- second_account_verify_balance_session_id
- second
stages:
- name: Transactions start menu [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_transaction_session_id}"
phoneNumber: "{first_account_phone_number}"
text: ""
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '51'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Balance {gift_value} {token_symbol}\n1. Send\n2. My Account\n3. Help"
- name: Transactions start menu [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_transaction_session_id}"
phoneNumber: "{second_account_phone_number}"
text: ""
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '56'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Salio {gift_value} {token_symbol}\n1. Tuma\n2. Akaunti yangu\n3. Usaidizi"
- name: Initate transcation attempt [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_transaction_session_id}"
phoneNumber: "{first_account_phone_number}"
text: "1"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '30'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Enter phone number\n0. Back"
- name: Initate transcation attempt [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_transaction_session_id}"
phoneNumber: "{second_account_phone_number}"
text: "1"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '33'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Weka nambari ya simu\n0. Nyuma"
- name: Enter phone number [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_transaction_session_id}"
phoneNumber: "{first_account_phone_number}"
text: "1*{second_account_phone_number}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '24'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Enter amount\n0. Back"
- name: Enter phone number [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_transaction_session_id}"
phoneNumber: "{second_account_phone_number}"
text: "1*{first_account_phone_number}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '25'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Weka kiwango\n0. Nyuma"
- name: Enter transcation amount [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_transaction_session_id}"
phoneNumber: "{first_account_phone_number}"
text: "1*{second_account_phone_number}*17"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON {second_account_given_name} {second_account_family_name} {second_account_phone_number} will receive 17.00 {token_symbol} from {first_account_given_name} {first_account_family_name} {first_account_phone_number}.\nPlease enter your PIN to confirm.\n0. Back"
- name: Enter transcation amount [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_transaction_session_id}"
phoneNumber: "{second_account_phone_number}"
text: "1*{first_account_phone_number}*25"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON {first_account_given_name} {first_account_family_name} {first_account_phone_number} atapokea 25.00 {token_symbol} kutoka kwa {second_account_given_name} {second_account_family_name} {second_account_phone_number}.\nTafadhali weka nambari yako ya siri kudhibitisha.\n0. Nyuma"
- name: Pin to authorize transaction [first account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_transaction_session_id}"
phoneNumber: "{first_account_phone_number}"
text: "1*{second_account_phone_number}*17*{first_account_pin_number}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Your request has been sent. {second_account_given_name} {second_account_family_name} {second_account_phone_number} will receive 17.00 {token_symbol} from {first_account_given_name} {first_account_family_name} {first_account_phone_number}.\n00. Back\n99. Exit"
- name: Pin to authorize transaction [second account]
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_transaction_session_id}"
phoneNumber: "{second_account_phone_number}"
text: "1*{first_account_phone_number}*25*{second_account_pin_number}"
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Ombi lako limetumwa. {first_account_given_name} {first_account_family_name} {first_account_phone_number} atapokea 25.00 {token_symbol} kutoka kwa {second_account_given_name} {second_account_family_name} {second_account_phone_number}.\n00. Nyuma\n99. Ondoka"
- name: Verify balance changes [first account]
delay_before: 10
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{first_account_verify_balance_session_id}"
phoneNumber: "{first_account_phone_number}"
text: ""
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '51'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Balance 58.00 {token_symbol}\n1. Send\n2. My Account\n3. Help"
- name: Verify balance changes [second account]
delay_before: 10
request:
url: "{server_url}"
data:
serviceCode: "*483*46#"
sessionId: "{second_account_verify_balance_session_id}"
phoneNumber: "{second_account_phone_number}"
text: ""
headers:
content-type: "application/x-www-form-urlencoded"
method: POST
response:
status_code:
- 200
headers:
Content-Length: '56'
Content-Type: "text/plain"
verify_response_with:
function: ext.validator:validate_response
extra_kwargs:
expected_response: "CON Salio 42.00 {token_symbol}\n1. Tuma\n2. Akaunti yangu\n3. Usaidizi"

View File

@@ -4,12 +4,13 @@
"source": "enter_gender",
"dest": "enter_location",
"after": "cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data",
"conditions": "cic_ussd.state_machine.logic.validator.is_valid_gender_selection"
"conditions": "cic_ussd.state_machine.logic.validator.is_valid_gender_selection",
"unless": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
},
{
"trigger": "scan_data",
"source": "enter_gender",
"dest": "standard_pin_authorization",
"dest": "gender_edit_pin_authorization",
"after": "cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data",
"conditions": [
"cic_ussd.state_machine.logic.validator.has_cached_user_metadata",
@@ -18,15 +19,14 @@
},
{
"trigger": "scan_data",
"source": "standard_pin_authorization",
"source": "gender_edit_pin_authorization",
"dest": "exit",
"conditions": "cic_ussd.state_machine.logic.pin.is_authorized_pin",
"after": "cic_ussd.state_machine.logic.user.edit_user_metadata_attribute",
"unless": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
"after": "cic_ussd.state_machine.logic.user.edit_user_metadata_attribute"
},
{
"trigger": "scan_data",
"source": "standard_pin_authorization",
"source": "gender_edit_pin_authorization",
"dest": "exit_pin_blocked",
"conditions": "cic_ussd.state_machine.logic.pin.is_locked_account"
},

View File

@@ -3,26 +3,26 @@
"trigger": "scan_data",
"source": "enter_location",
"dest": "enter_products",
"after": "cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data"
"after": "cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data",
"unless": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
},
{
"trigger": "scan_data",
"source": "enter_location",
"dest": "standard_pin_authorization",
"dest": "location_edit_pin_authorization",
"after": "cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data",
"conditions": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
},
{
"trigger": "scan_data",
"source": "standard_pin_authorization",
"source": "location_edit_pin_authorization",
"dest": "exit",
"conditions": "cic_ussd.state_machine.logic.pin.is_authorized_pin",
"after": "cic_ussd.state_machine.logic.user.edit_user_metadata_attribute",
"unless": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
"after": "cic_ussd.state_machine.logic.user.edit_user_metadata_attribute"
},
{
"trigger": "scan_data",
"source": "standard_pin_authorization",
"source": "location_edit_pin_authorization",
"dest": "exit_pin_blocked",
"conditions": "cic_ussd.state_machine.logic.pin.is_locked_account"
}

View File

@@ -7,49 +7,28 @@
},
{
"trigger": "scan_data",
"source": "enter_given_name",
"dest": "standard_pin_authorization",
"source": "enter_family_name",
"dest": "name_edit_pin_authorization",
"after": "cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data",
"conditions": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
},
{
"trigger": "scan_data",
"source": "standard_pin_authorization",
"dest": "exit",
"conditions": "cic_ussd.state_machine.logic.pin.is_authorized_pin",
"after": "cic_ussd.state_machine.logic.user.edit_user_metadata_attribute",
"unless": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
},
{
"trigger": "scan_data",
"source": "standard_pin_authorization",
"dest": "exit_pin_blocked",
"conditions": "cic_ussd.state_machine.logic.pin.is_locked_account"
},
{
"trigger": "scan_data",
"source": "enter_family_name",
"dest": "enter_gender",
"after": "cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data"
},
{
"trigger": "scan_data",
"source": "enter_family_name",
"dest": "standard_pin_authorization",
"after": "cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data",
"conditions": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
},
{
"trigger": "scan_data",
"source": "standard_pin_authorization",
"dest": "exit",
"conditions": "cic_ussd.state_machine.logic.pin.is_authorized_pin",
"after": "cic_ussd.state_machine.logic.user.edit_user_metadata_attribute",
"unless": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
},
{
"trigger": "scan_data",
"source": "standard_pin_authorization",
"source": "name_edit_pin_authorization",
"dest": "exit",
"conditions": "cic_ussd.state_machine.logic.pin.is_authorized_pin",
"after": "cic_ussd.state_machine.logic.user.edit_user_metadata_attribute"
},
{
"trigger": "scan_data",
"source": "name_edit_pin_authorization",
"dest": "exit_pin_blocked",
"conditions": "cic_ussd.state_machine.logic.pin.is_locked_account"
}

View File

@@ -9,14 +9,14 @@
"trigger": "scan_data",
"source": "enter_current_pin",
"dest": "exit_pin_blocked",
"conditions": "cic_ussd.state_machine.logic.menu.is_blocked_pin"
"conditions": "cic_ussd.state_machine.logic.pin.is_blocked_pin"
},
{
"trigger": "scan_data",
"source": "enter_new_pin",
"dest": "new_pin_confirmation",
"after": "cic_ussd.state_machine.logic.pin.save_initial_pin_to_session_data",
"conditions": "cic_ussd.state_machine.logic.menu.is_valid_new_pin"
"conditions": "cic_ussd.state_machine.logic.pin.is_valid_new_pin"
},
{
"trigger": "scan_data",
@@ -28,7 +28,7 @@
"source": "new_pin_confirmation",
"dest": "complete",
"conditions": "cic_ussd.state_machine.logic.pin.pins_match",
"after": "cic_ussd.state_machine.logic.menu.complete_pin_change"
"after": "cic_ussd.state_machine.logic.pin.complete_pin_change"
},
{
"trigger": "scan_data",

View File

@@ -2,7 +2,7 @@
{
"trigger": "scan_data",
"source": "enter_products",
"dest": "standard_pin_authorization",
"dest": "products_edit_pin_authorization",
"conditions": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata",
"after": "cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data"
},
@@ -13,18 +13,19 @@
"after": [
"cic_ussd.state_machine.logic.user.save_metadata_attribute_to_session_data",
"cic_ussd.state_machine.logic.user.save_complete_user_metadata"
]
],
"unless": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
},
{
"trigger": "scan_data",
"source": "standard_pin_authorization",
"source": "products_edit_pin_authorization",
"dest": "exit",
"conditions": "cic_ussd.state_machine.logic.pin.is_authorized_pin",
"after": "cic_ussd.state_machine.logic.user.edit_user_metadata_attribute"
},
{
"trigger": "scan_data",
"source": "standard_pin_authorization",
"source": "products_edit_pin_authorization",
"dest": "exit_pin_blocked",
"conditions": "cic_ussd.state_machine.logic.pin.is_locked_account"
}

View File

@@ -36,26 +36,12 @@
"source": "initial_pin_entry",
"dest": "exit_invalid_pin"
},
{
"trigger": "scan_data",
"source": "initial_pin_confirmation",
"dest": "start",
"conditions": [
"cic_ussd.state_machine.logic.pin.pins_match",
"cic_ussd.state_machine.logic.validator.has_cached_user_metadata"
],
"after": [
"cic_ussd.state_machine.logic.pin.complete_pin_change",
"cic_ussd.state_machine.logic.user.get_user_metadata",
"cic_ussd.state_machine.logic.user.update_account_status_to_active"
]
},
{
"trigger": "scan_data",
"source": "initial_pin_confirmation",
"unless": "cic_ussd.state_machine.logic.validator.has_cached_user_metadata",
"conditions": "cic_ussd.state_machine.logic.pin.pins_match",
"dest": "enter_given_name",
"dest": "start",
"after": [
"cic_ussd.state_machine.logic.pin.complete_pin_change",
"cic_ussd.state_machine.logic.user.update_account_status_to_active"

View File

@@ -8,7 +8,7 @@
{
"trigger": "scan_data",
"source": "metadata_management",
"dest": "enter_age",
"dest": "enter_gender",
"conditions": "cic_ussd.state_machine.logic.menu.menu_two_selected"
},
{

View File

@@ -6,7 +6,7 @@ en:
2. Kiswahili
3. Help
initial_pin_entry: |-
CON Please enter a new four number PIN for your account.
CON Please enter a new four number PIN for your account.
0. Back
initial_pin_confirmation: |-
CON Enter your four number PIN again
@@ -76,7 +76,10 @@ en:
CON Enter current PIN. You have %{remaining_attempts} attempts remaining.
0. Back
enter_new_pin: |-
CON Enter new PIN again
CON Enter your new four number PIN
0. Back
new_pin_confirmation: |-
CON Enter your new four number PIN again
0. Back
transaction_pin_authorization:
first: |-
@@ -107,6 +110,34 @@ en:
retry: |-
CON Please enter your PIN. You have %{remaining_attempts} attempts remaining
0. Back
name_edit_pin_authorization:
first: |-
CON Please enter your PIN
0. Back
retry: |-
CON Please enter your PIN. You have %{remaining_attempts} attempts remaining
0. Back
gender_edit_pin_authorization:
first: |-
CON Please enter your PIN
0. Back
retry: |-
CON Please enter your PIN. You have %{remaining_attempts} attempts remaining
0. Back
location_edit_pin_authorization:
first: |-
CON Please enter your PIN
0. Back
retry: |-
CON Please enter your PIN. You have %{remaining_attempts} attempts remaining
0. Back
products_edit_pin_authorization:
first: |-
CON Please enter your PIN
0. Back
retry: |-
CON Please enter your PIN. You have %{remaining_attempts} attempts remaining
0. Back
account_balances: |-
CON Your balances are as follows:
balance: %{operational_balance} %{token_symbol}

View File

@@ -21,7 +21,7 @@ sw:
CON Weka jinsia yako
1. Mwanaume
2. Mwanamke
3. Nyngine
3. Nyngine
0. Nyuma
enter_location: |-
CON Weka eneo lako
@@ -61,7 +61,7 @@ sw:
Jina: %{full_name}
Jinsia: %{gender}
Eneo: %{location}
Unauza: %{user_bio}
Unauza: %{products}
0. Nyuma
select_preferred_language: |-
CON Chagua lugha
@@ -78,6 +78,9 @@ sw:
enter_new_pin: |-
CON Weka nambari ya siri mpya
0. Nyuma
new_pin_confirmation: |-
CON Weka PIN yako tena
0. Nyuma
transaction_pin_authorization:
first: |-
CON %{recipient_information} atapokea %{transaction_amount} %{token_symbol} kutoka kwa %{sender_information}.
@@ -86,9 +89,9 @@ sw:
retry: |-
CON Weka nambari ya siri. Una majaribio %{remaining_attempts} yaliyobaki.
0. Nyuma
standard_pin_authorization:
display_metadata_pin_authorization:
first: |-
CON Tafadhali weka PIN yako.
CON Tafadhali weka PIN yako
0. Nyuma
retry: |-
CON Tafadhali weka PIN yako. Una majaribio %{remaining_attempts} yaliyobaki.
@@ -107,12 +110,40 @@ sw:
retry: |-
CON Tafadhali weka PIN yako. Una majaribio %{remaining_attempts} yaliyobaki.
0. Nyuma
name_edit_pin_authorization:
first: |-
CON Tafadhali weka PIN yako
0. Nyuma
retry: |-
CON Tafadhali weka PIN yako. Una majaribio %{remaining_attempts} yaliyobaki.
0. Nyuma
gender_edit_pin_authorization:
first: |-
CON Tafadhali weka PIN yako
0. Nyuma
retry: |-
CON Tafadhali weka PIN yako. Una majaribio %{remaining_attempts} yaliyobaki.
0. Nyuma
location_edit_pin_authorization:
first: |-
CON Tafadhali weka PIN yako
0. Nyuma
retry: |-
CON Tafadhali weka PIN yako. Una majaribio %{remaining_attempts} yaliyobaki.
0. Nyuma
products_edit_pin_authorization:
first: |-
CON Tafadhali weka PIN yako
0. Nyuma
retry: |-
CON Tafadhali weka PIN yako. Una majaribio %{remaining_attempts} yaliyobaki.
0. Nyuma
account_balances: |-
CON Salio zako ni zifuatazo:
salio: %{operational_balance}
ushuru: %{tax}
tuzo: %{bonus}
0. Back
salio: %{operational_balance} %{token_symbol}
ushuru: %{tax} %{token_symbol}
tuzo: %{bonus} %{token_symbol}
0. Nyuma
first_transaction_set: |-
CON %{first_transaction_set}
1. Mbele

View File

@@ -1,19 +1,19 @@
# syntax = docker/dockerfile:1.2
FROM python:3.8.6-slim-buster as compile-image
#FROM python:3.8.6-slim-buster as dev
FROM registry.gitlab.com/grassrootseconomics/cic-base-images:python-3.8.6-dev as dev
RUN apt-get update
RUN apt-get install -y --no-install-recommends git gcc g++ libpq-dev gawk jq telnet wget openssl iputils-ping gnupg socat bash procps make python2 cargo
RUN apt-get install -y cargo
RUN touch /etc/apt/sources.list.d/ethereum.list
RUN echo 'deb http://ppa.launchpad.net/ethereum/ethereum/ubuntu bionic main' > /etc/apt/sources.list.d/ethereum.list
RUN echo 'deb-src http://ppa.launchpad.net/ethereum/ethereum/ubuntu bionic main' >> /etc/apt/sources.list.d/ethereum.list
RUN cat etc/apt/sources.list.d/ethereum.list
RUN cat /etc/apt/sources.list.d/ethereum.list
RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 2A518C819BE37D2C2031944D1C52189C923F6CA9
RUN apt-get update
RUN apt-get install solc
RUN pip install --upgrade pip
WORKDIR /root
RUN mkdir -vp /usr/local/etc/cic
@@ -51,19 +51,14 @@ ENV PATH $NVM_DIR/versions/node//v$NODE_VERSION/bin:$PATH
# WORKDIR /home/grassroots
# USER grassroots
COPY contract-migration/requirements.txt .
ARG pip_extra_args=""
ARG pip_index_url=https://pypi.org/simple
ARG pip_extra_index_url=https://pip.grassrootseconomics.net:8433
ARG cic_base_version=0.1.2b11
ARG cic_eth_version=0.11.0b14
ARG sarafu_token_version=0.0.1a8
ARG sarafu_faucet_version=0.0.3a3
RUN pip install --index-url https://pypi.org/simple --extra-index-url $pip_extra_index_url \
cic-base[full_graph]==$cic_base_version \
cic-eth==$cic_eth_version \
sarafu-faucet==$sarafu_faucet_version \
sarafu-token==$sarafu_token_version \
cic-eth==$cic_eth_version
ARG pip_gitlab_extra_index_url=https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple
RUN pip install --index-url https://pypi.org/simple \
--extra-index-url $pip_extra_index_url --extra-index-url pip_gitlab_extra_index_url -r requirements.txt
# -------------- begin runtime container ----------------
FROM python:3.8.6-slim-buster as runtime-image
@@ -72,9 +67,9 @@ RUN apt-get update
RUN apt-get install -y --no-install-recommends gnupg libpq-dev
RUN apt-get install -y jq bash iputils-ping socat
COPY --from=compile-image /usr/local/bin/ /usr/local/bin/
COPY --from=compile-image /usr/local/etc/cic/ /usr/local/etc/cic/
COPY --from=compile-image /usr/local/lib/python3.8/site-packages/ \
COPY --from=dev /usr/local/bin/ /usr/local/bin/
COPY --from=dev /usr/local/etc/cic/ /usr/local/etc/cic/
COPY --from=dev /usr/local/lib/python3.8/site-packages/ \
/usr/local/lib/python3.8/site-packages/
ENV EXTRA_INDEX_URL https://pip.grassrootseconomics.net:8433

View File

@@ -0,0 +1,4 @@
cic-base[full_graph]==0.1.2b15
sarafu-faucet==0.0.3a3
sarafu-token==0.0.1a8
cic-eth==0.11.0b16

View File

@@ -47,7 +47,7 @@ EOF
>&2 echo "create account for gas gifter"
old_gas_provider=$DEV_ETH_ACCOUNT_GAS_PROVIDER
DEV_ETH_ACCOUNT_GAS_GIFTER=`cic-eth-create $debug --redis-host-callback=$REDIS_HOST --redis-port-callback=$REDIS_PORT --no-register`
DEV_ETH_ACCOUNT_GAS_GIFTER=`cic-eth-create --timeout 120 $debug --redis-host-callback=$REDIS_HOST --redis-port-callback=$REDIS_PORT --no-register`
echo DEV_ETH_ACCOUNT_GAS_GIFTER=$DEV_ETH_ACCOUNT_GAS_GIFTER >> $env_out_file
cic-eth-tag -i $CIC_CHAIN_SPEC GAS_GIFTER $DEV_ETH_ACCOUNT_GAS_GIFTER

View File

@@ -259,4 +259,6 @@ Should exit with code 0 if all input data is found in the respective services.
- Running the balance script should be _optional_ in all cases, but is currently required in the case of `cic_ussd` because it is needed to generate the metadata. An improvement would be moving the task to `import_users.py`, for a different queue than the balance tx handler.
- MacOS BigSur issue when installing psycopg2: ld: library not found for -lssl -> https://github.com/psycopg/psycopg2/issues/1115#issuecomment-831498953
- `cic_ussd` imports is poorly implemented, and consumes a lot of resources. Therefore it takes a long time to complete. Reducing the amount of polls for the phone pointer would go a long way to improve it.

View File

@@ -1,5 +1,5 @@
cic-base[full_graph]==0.1.2b11
cic-base[full_graph]==0.1.2b15
sarafu-faucet==0.0.3a3
cic-eth==0.11.0b15
cic-eth==0.11.0b16
cic-types==0.1.0a11
crypto-dev-signer==0.4.14b3

View File

@@ -6,11 +6,7 @@ volumes:
bee-data: {}
signer-data: {}
bloxberg-data: {}
contract-config:
driver_opts:
type: local
o: bind
device: ./service-configs
contract-config: {}
networks:
default: