diff --git a/apps/cic-eth/cic_eth/pytest/helpers/accounts.py b/apps/cic-eth/cic_eth/pytest/helpers/accounts.py new file mode 100644 index 00000000..74c408d9 --- /dev/null +++ b/apps/cic-eth/cic_eth/pytest/helpers/accounts.py @@ -0,0 +1,12 @@ +# standard imports +import os +import random +import uuid + + + + + +def blockchain_address() -> str: + return os.urandom(20).hex().lower() + diff --git a/apps/cic-eth/cic_eth/pytest/patches/__init__.py b/apps/cic-eth/cic_eth/pytest/patches/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/cic-eth/cic_eth/pytest/patches/account.py b/apps/cic-eth/cic_eth/pytest/patches/account.py new file mode 100644 index 00000000..0720d3ac --- /dev/null +++ b/apps/cic-eth/cic_eth/pytest/patches/account.py @@ -0,0 +1,132 @@ +# standard imports +import os + +# external imports +import pytest +from celery import uuid +# test imports +from cic_eth.pytest.helpers.accounts import blockchain_address + + +@pytest.fixture(scope='function') +def task_uuid(): + return uuid() + +@pytest.fixture(scope='function') +def default_token_data(foo_token_symbol, foo_token): + return { + 'symbol': foo_token_symbol, + 'address': foo_token, + 'name': 'Giftable Token', + 'decimals': 6, + "converters": [] + } + +@pytest.fixture(scope='function') +def mock_account_creation_task_request(mocker, task_uuid): + def mock_request(self): + mocked_task_request = mocker.patch('celery.app.task.Task.request') + mocked_task_request.id = task_uuid + return mocked_task_request + mocker.patch('cic_eth.api.api_task.Api.create_account', mock_request) + + +@pytest.fixture(scope='function') +def mock_account_creation_task_result(mocker, task_uuid): + def task_result(self): + sync_res = mocker.patch('celery.result.AsyncResult') + sync_res.id = task_uuid + sync_res.get.return_value = blockchain_address() + return sync_res + mocker.patch('cic_eth.api.api_task.Api.create_account', task_result) + +@pytest.fixture(scope='function') +def mock_token_api_query(foo_token_symbol, foo_token, mocker, task_uuid): + def mock_query(self, token_symbol, proof=None): + sync_res = mocker.patch('celery.result.AsyncResult') + sync_res.id = task_uuid + sync_res.get.return_value = [ + { + 'address': foo_token, + 'converters': [], + 'decimals': 6, + 'name': 'Giftable Token', + 'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'], + 'symbol': foo_token_symbol, + },{'5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C','Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']} + ] + return sync_res + mocker.patch('cic_eth.api.api_task.Api.token', mock_query) + +@pytest.fixture(scope='function') +def mock_tokens_api_query(foo_token_symbol, foo_token, mocker, task_uuid): + def mock_query(self, token_symbol, proof=None): + sync_res = mocker.patch('celery.result.AsyncResult') + sync_res.id = task_uuid + sync_res.get.return_value = [[ + { + 'address': foo_token, + 'converters': [], + 'decimals': 6, + 'name': 'Giftable Token', + 'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'], + 'symbol': foo_token_symbol, + },{'5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C','Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']} + ], [ + { + 'address': foo_token, + 'converters': [], + 'decimals': 6, + 'name': 'Giftable Token', + 'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'], + 'symbol': foo_token_symbol, + },{'5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C','Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']} + ]] + return sync_res + mocker.patch('cic_eth.api.api_task.Api.tokens', mock_query) + + +@pytest.fixture(scope='function') +def mock_sync_balance_api_query(balances, mocker, task_uuid): + def sync_api_query(self, address: str, token_symbol: str): + sync_res = mocker.patch('celery.result.AsyncResult') + sync_res.id = task_uuid + sync_res.get.return_value = balances + return sync_res + mocker.patch('cic_eth.api.api_task.Api.balance', sync_api_query) + + +@pytest.fixture(scope='function') +def mock_sync_default_token_api_query(default_token_data, mocker, task_uuid): + def mock_query(self): + sync_res = mocker.patch('celery.result.AsyncResult') + sync_res.id = task_uuid + sync_res.get.return_value = default_token_data + return sync_res + mocker.patch('cic_eth.api.api_task.Api.default_token', mock_query) + + +@pytest.fixture(scope='function') +def mock_transaction_list_query(mocker): + query_args = {} + + def mock_query(self, address: str, limit: int): + query_args['address'] = address + query_args['limit'] = limit + + mocker.patch('cic_eth.api.api_task.Api.list', mock_query) + return query_args + + +@pytest.fixture(scope='function') +def mock_transfer_api(mocker): + transfer_args = {} + + def mock_transfer(self, from_address: str, to_address: str, value: int, token_symbol: str): + transfer_args['from_address'] = from_address + transfer_args['to_address'] = to_address + transfer_args['value'] = value + transfer_args['token_symbol'] = token_symbol + + mocker.patch('cic_eth.api.api_task.Api.transfer', mock_transfer) + return transfer_args diff --git a/apps/cic-eth/cic_eth/runnable/daemons/server.py b/apps/cic-eth/cic_eth/runnable/daemons/server.py index c0eb29fc..33903df4 100644 --- a/apps/cic-eth/cic_eth/runnable/daemons/server.py +++ b/apps/cic-eth/cic_eth/runnable/daemons/server.py @@ -1,9 +1,20 @@ +import logging + import cic_eth.cli from cic_eth.server.app import create_app -from cic_eth.server.config import get_config +from cic_eth.server.celery import create_celery_wrapper - -config = get_config() +arg_flags = cic_eth.cli.argflag_std_base +local_arg_flags = cic_eth.cli.argflag_local_taskcallback +argparser = cic_eth.cli.ArgumentParser(arg_flags) +argparser.process_local_flags(local_arg_flags) +args = argparser.parse_args() +config = cic_eth.cli.Config.from_args(args, arg_flags, local_arg_flags) +# Define log levels +if args.vv: + logging.getLogger().setLevel(logging.DEBUG) +elif args.v: + logging.getLogger().setLevel(logging.INFO) # Setup Celery App @@ -18,13 +29,9 @@ redis_port = config.get('REDIS_PORT') redis_db = config.get('REDIS_DB') redis_timeout = config.get('REDIS_TIMEOUT') - -app = create_app(chain_spec, - celery_queue, - redis_host, - redis_port, - redis_db, - redis_timeout) +celery_wrapper = create_celery_wrapper(celery_queue=celery_queue, chain_spec=chain_spec, + redis_db=redis_db, redis_host=redis_host, redis_port=redis_port, redis_timeout=redis_timeout) +app = create_app(celery_wrapper) if __name__ == "__main__": import uvicorn diff --git a/apps/cic-eth/cic_eth/server/app.py b/apps/cic-eth/cic_eth/server/app.py index 53321561..793258ce 100644 --- a/apps/cic-eth/cic_eth/server/app.py +++ b/apps/cic-eth/cic_eth/server/app.py @@ -12,16 +12,8 @@ from fastapi import FastAPI, Query log = logging.getLogger(__name__) -def create_app(chain_spec, - celery_queue, - redis_host, - redis_port, - redis_db, - redis_timeout): - setup_cache(redis_db=redis_db, redis_host=redis_host, - redis_port=redis_port) - celery_wrapper = create_celery_wrapper(celery_queue=celery_queue, chain_spec=chain_spec, - redis_db=redis_db, redis_host=redis_host, redis_port=redis_port, redis_timeout=redis_timeout) +def create_app(celery_wrapper): + app = FastAPI(debug=True, title="Grassroots Economics", description="CIC ETH API", @@ -99,7 +91,6 @@ def create_app(chain_spec, if token == None: sys.stderr.write(f"Cached Token {token_symbol} not found") data = celery_wrapper('token', token_symbol, proof=proof) - cache.set_token_data(token_symbol, data) token = Token.new(data) sys.stderr.write(f"Token {token}") @@ -119,20 +110,10 @@ def create_app(chain_spec, @app.get("/default_token", response_model=DefaultToken) def default_token(): - data = cache.get_default_token() - if data is None: - data = celery_wrapper('default_token') - if data is not None: - cache.set_default_token(data) + data = celery_wrapper('default_token') return data def get_token(token_symbol: str): - data = cache.get_token_data(token_symbol) - log.debug(f"cached token data: {data}") - if data == None: - data = celery_wrapper('token', token_symbol) - log.debug( - f"No token data setting token data for: {token_symbol} to {data}") - cache.set_token_data(token_symbol, data) + data = celery_wrapper('token', token_symbol) return Token.new(data) return app diff --git a/apps/cic-eth/cic_eth/server/celery.py b/apps/cic-eth/cic_eth/server/celery.py index 8271a372..e55c1ec5 100644 --- a/apps/cic-eth/cic_eth/server/celery.py +++ b/apps/cic-eth/cic_eth/server/celery.py @@ -18,7 +18,7 @@ def create_celery_wrapper(chain_spec, def call(method, *args, catch=1, **kwargs): """ Creates a redis channel and calls `cic_eth.api` with the provided `method` and `*args`. Returns the result of the api call. Catch allows you to specify how many messages to catch before returning. """ - log.debug(f"Using chainspec: {chain_spec}") + log.debug(f"Using redis: {redis_host}, {redis_port}, {redis_db}") redis_channel = str(uuid.uuid4()) r = redis.Redis(redis_host, redis_port, redis_db) ps = r.pubsub() @@ -39,6 +39,7 @@ def create_celery_wrapper(chain_spec, if catch == 1: message = ps.get_message(timeout=redis_timeout) data = json.loads(message['data'])["result"] + raise data else: for _i in range(catch): message = ps.get_message( diff --git a/apps/cic-eth/cic_eth/server/config.py b/apps/cic-eth/cic_eth/server/config.py deleted file mode 100644 index f10ef0a4..00000000 --- a/apps/cic-eth/cic_eth/server/config.py +++ /dev/null @@ -1,20 +0,0 @@ -import logging - -import cic_eth.cli - - -def get_config(): - # Parse Args - arg_flags = cic_eth.cli.argflag_std_base - local_arg_flags = cic_eth.cli.argflag_local_taskcallback - argparser = cic_eth.cli.ArgumentParser(arg_flags) - argparser.process_local_flags(local_arg_flags) - args = argparser.parse_args([]) - config = cic_eth.cli.Config.from_args(args, arg_flags, local_arg_flags) - # Define log levels - if args.vv: - logging.getLogger().setLevel(logging.DEBUG) - elif args.v: - logging.getLogger().setLevel(logging.INFO) - - return config diff --git a/apps/cic-eth/tests/conftest.py b/apps/cic-eth/tests/conftest.py index a0cc41d3..a6aa6946 100644 --- a/apps/cic-eth/tests/conftest.py +++ b/apps/cic-eth/tests/conftest.py @@ -24,6 +24,8 @@ from cic_eth.pytest.fixtures_database import * from cic_eth.pytest.fixtures_role import * from cic_eth.pytest.fixtures_contract import * from cic_eth.pytest.fixtures_token import * +from cic_eth.pytest.patches.account import * + from chainlib.eth.pytest import * from eth_contract_registry.pytest import * from cic_eth_registry.pytest.fixtures_contracts import * diff --git a/apps/cic-eth/tests/run_tests.sh b/apps/cic-eth/tests/run_tests.sh index 751f42cd..f8e80421 100644 --- a/apps/cic-eth/tests/run_tests.sh +++ b/apps/cic-eth/tests/run_tests.sh @@ -2,9 +2,9 @@ set -e -pip install --extra-index-url https://pip.grassrootseconomics.net --extra-index-url https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple --r admin_requirements.txt --r services_requirements.txt +pip install --extra-index-url https://pip.grassrootseconomics.net --extra-index-url https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple \ +-r admin_requirements.txt \ +-r services_requirements.txt \ -r test_requirements.txt export PYTHONPATH=. && pytest -x --cov=cic_eth --cov-fail-under=90 --cov-report term-missing tests diff --git a/apps/cic-eth/tests/test_server.py b/apps/cic-eth/tests/test_server.py index 0969b380..e19df959 100644 --- a/apps/cic-eth/tests/test_server.py +++ b/apps/cic-eth/tests/test_server.py @@ -5,89 +5,83 @@ import logging import time import hexathon +import pytest from cic_eth.server.app import create_app -from cic_eth_registry.pytest.fixtures_tokens import * +from cic_eth.server.celery import create_celery_wrapper from fastapi.testclient import TestClient log = logging.getLogger(__name__) -@pytest.fixture(scope='session') -def client(config, default_chain_spec, celery_session_worker): - config.add(str(default_chain_spec), 'CHAIN_SPEC', exists_ok=True) - config.add('cic_eth_test', 'CELERY_QUEUE', exists_ok=True) - chain_spec = config.get('CHAIN_SPEC') - celery_queue = config.get('CELERY_QUEUE') - redis_host = config.get('REDIS_REDIS_HOST') - redis_port = config.get('REDIS_REDIS_PORT') - redis_db = config.get('REDIS_DB') - redis_timeout = 20 +@pytest.fixture(scope='function') +def celery_wrapper(api): + """ Creates a redis channel and calls `cic_eth.api` with the provided `method` and `*args`. Returns the result of the api call. Catch allows you to specify how many messages to catch before returning. + """ + def wrapper(method, *args, catch=1, **kwargs): + t = getattr(api, method)(*args, **kwargs) + return t.get() + return wrapper - app = create_app(str(chain_spec), celery_queue, redis_host, - redis_port, redis_db, redis_timeout) +@pytest.fixture(scope='function') +def client(celery_wrapper): + app = create_app(celery_wrapper) return TestClient(app) -def test_default_token(client, foo_token, default_chain_spec): - print(foo_token) - print(f"default_chain_spec: {default_chain_spec.asdict()}") +def test_default_token(client,mock_sync_default_token_api_query): # Default Token response = client.get('/default_token') log.debug(f"balance response {response}") default_token = response.json() - assert default_token == { - 'address': '3FF776B6f888980DEf9d4220858803f9dC5e341e', - 'decimals': 7, - 'name': 'Giftable Token', - 'symbol': 'GFT', - } + assert default_token == {'symbol': 'FOO', 'address': '0xe7c559c40B297d7f039767A2c3677E20B24F1385', 'name': 'Giftable Token', 'decimals': 6} - -def test_token(client): +def test_token(client, mock_token_api_query): # Default Token response = client.get('/token?token_symbol=FOO') log.debug(f"token response {response}") token = response.json() assert token == { - 'address': '3ff776b6f888980def9d4220858803f9dc5e341e', + 'address': '0xe7c559c40B297d7f039767A2c3677E20B24F1385', 'converters': [], 'decimals': 6, 'name': 'Giftable Token', - 'proofs': ['3af82fa124235f84e78145f008054b11fe477e2b043ac5e4979c3afa737fd328'], - 'proofs_with_signers': [{'proof': '3af82fa124235f84e78145f008054b11fe477e2b043ac5e4979c3afa737fd328', - 'signers': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']}], - 'symbol': 'GFT', + 'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'], + 'proofs_with_signers': [{'proof': '5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3', + 'signers': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C', 'Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']}], + 'symbol': 'FOO', } - -def test_tokens(client): +def test_tokens(client, mock_tokens_api_query): # Default Token response = client.get( - '/tokens', params={'token_symbols': ['GFT', 'COFE', 'FOO']}) + '/tokens', params={'token_symbols': ['FOO', 'FOO']}) log.debug(f"tokens response {response}") tokens = response.json() assert tokens == [ - {'address': '3ff776b6f888980def9d4220858803f9dc5e341e', - 'converters': [], - 'decimals': 6, - 'name': 'Giftable Token', - 'proofs': ['3af82fa124235f84e78145f008054b11fe477e2b043ac5e4979c3afa737fd328'], - 'proofs_with_signers': [{'proof': '3af82fa124235f84e78145f008054b11fe477e2b043ac5e4979c3afa737fd328', - 'signers': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']}], - 'symbol': 'GFT'}, - {'address': '9c9506cf6c50f5b0371be72920fc6060d1a88a6a', - 'converters': [], - 'decimals': 6, - 'name': 'Coffee', - 'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'], - 'proofs_with_signers': [{'proof': '5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3', - 'signers': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C', - 'Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']}], - 'symbol': 'COFE'}, + { + 'address': '0xe7c559c40B297d7f039767A2c3677E20B24F1385', + 'converters': [], + 'decimals': 6, + 'name': 'Giftable Token', + 'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'], + 'proofs_with_signers': [{'proof': '5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3', + 'signers': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C', 'Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']}], + 'symbol': 'FOO', + }, + { + 'address': '0xe7c559c40B297d7f039767A2c3677E20B24F1385', + 'converters': [], + 'decimals': 6, + 'name': 'Giftable Token', + 'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'], + 'proofs_with_signers': [{'proof': '5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3', + 'signers': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C', 'Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']}], + 'symbol': 'FOO', + }, ] - +@pytest.mark.skip("Not implemented") def test_account(client): # Default Token response = client.get('/default_token')