diff --git a/tests/test_analyze.py b/tests/test_analyze.py index 48d3269..ca40943 100644 --- a/tests/test_analyze.py +++ b/tests/test_analyze.py @@ -1,88 +1,43 @@ -from tokenomics_decentralization.analyze import get_output_row, analyze_snapshot, analyze, write_csv_output +from tokenomics_decentralization.analyze import analyze_snapshot, analyze, get_entries +from unittest.mock import call import pathlib -import os - - -def test_get_output_row(mocker): - get_metrics_mock = mocker.patch('tokenomics_decentralization.helper.get_metrics') - get_metrics_mock.return_value = ['hhi', 'gini'] - - get_clustering_mock = mocker.patch('tokenomics_decentralization.helper.get_clustering_flag') - get_exclude_contracts_mock = mocker.patch('tokenomics_decentralization.helper.get_exclude_contracts_flag') - get_exclude_below_fees_mock = mocker.patch('tokenomics_decentralization.helper.get_exclude_below_fees_flag') - get_exclude_below_usd_cent_mock = mocker.patch('tokenomics_decentralization.helper.get_exclude_below_usd_cent_flag') - get_top_limit_type_mock = mocker.patch('tokenomics_decentralization.helper.get_top_limit_type') - get_top_limit_value_mock = mocker.patch('tokenomics_decentralization.helper.get_top_limit_value') - - get_clustering_mock.return_value = True - get_exclude_contracts_mock.return_value = False - get_exclude_below_fees_mock.return_value = False - get_exclude_below_usd_cent_mock.return_value = False - get_top_limit_type_mock.return_value = 'absolute' - get_top_limit_value_mock.return_value = 0 - - metrics = {'hhi': 1, 'gini': 0} - csv_row = get_output_row('bitcoin', '2010-01-01', metrics) - assert csv_row == ['bitcoin', '2010-01-01', True, False, 'absolute', 0, False, False, 1, 0] - - get_clustering_mock.return_value = False - metrics = {'non-clustered hhi': 1, 'non-clustered gini': 0} - csv_row = get_output_row('bitcoin', '2010-01-01', metrics) - assert csv_row == ['bitcoin', '2010-01-01', False, False, 'absolute', 0, False, False, 1, 0] - - get_exclude_contracts_mock.return_value = True - metrics = {'exclude_contracts non-clustered hhi': 1, 'exclude_contracts non-clustered gini': 0} - csv_row = get_output_row('bitcoin', '2010-01-01', metrics) - assert csv_row == ['bitcoin', '2010-01-01', False, True, 'absolute', 0, False, False, 1, 0] - - get_top_limit_value_mock.return_value = 1 - metrics = {'top-1_absolute exclude_contracts non-clustered hhi': 1, 'top-1_absolute exclude_contracts non-clustered gini': 0} - csv_row = get_output_row('bitcoin', '2010-01-01', metrics) - assert csv_row == ['bitcoin', '2010-01-01', False, True, 'absolute', 1, False, False, 1, 0] - - get_exclude_below_fees_mock.return_value = True - get_top_limit_value_mock.return_value = 1 - metrics = {'top-1_absolute exclude_below_fees exclude_contracts non-clustered hhi': 1, 'top-1_absolute exclude_below_fees exclude_contracts non-clustered gini': 0} - csv_row = get_output_row('bitcoin', '2010-01-01', metrics) - assert csv_row == ['bitcoin', '2010-01-01', False, True, 'absolute', 1, True, False, 1, 0] def test_analyze_snapshot(mocker): - get_force_analyze_mock = mocker.patch('tokenomics_decentralization.helper.get_force_analyze_flag') get_clustering_mock = mocker.patch('tokenomics_decentralization.helper.get_clustering_flag') get_exclude_contracts_mock = mocker.patch('tokenomics_decentralization.helper.get_exclude_contracts_flag') get_exclude_below_fees_mock = mocker.patch('tokenomics_decentralization.helper.get_exclude_below_fees_flag') get_exclude_below_usd_cent_mock = mocker.patch('tokenomics_decentralization.helper.get_exclude_below_usd_cent_flag') - get_median_tx_fee_mock = mocker.patch('tokenomics_decentralization.helper.get_median_tx_fee') get_top_limit_type_mock = mocker.patch('tokenomics_decentralization.helper.get_top_limit_type') get_top_limit_value_mock = mocker.patch('tokenomics_decentralization.helper.get_top_limit_value') + get_metrics_mock = mocker.patch('tokenomics_decentralization.helper.get_metrics') - get_snapshot_info_mock = mocker.patch('tokenomics_decentralization.db_helper.get_snapshot_info') - get_metric_value_mock = mocker.patch('tokenomics_decentralization.db_helper.get_metric_value') - get_clustered_entries_mock = mocker.patch('tokenomics_decentralization.db_helper.get_balance_entries') - get_nonclustered_entries_mock = mocker.patch('tokenomics_decentralization.db_helper.get_non_clustered_balance_entries') - db_insert_mock = mocker.patch('tokenomics_decentralization.db_helper.insert_metric') - db_commit_mock = mocker.patch('tokenomics_decentralization.db_helper.commit_database') compute_hhi_mock = mocker.patch('tokenomics_decentralization.analyze.compute_hhi') compute_tau_mock = mocker.patch('tokenomics_decentralization.analyze.compute_tau') - get_force_analyze_mock.return_value = False get_clustering_mock.return_value = True get_exclude_contracts_mock.return_value = False get_exclude_below_fees_mock.return_value = False get_exclude_below_usd_cent_mock.return_value = False - get_median_tx_fee_mock.return_value = 0 get_top_limit_type_mock.return_value = 'absolute' get_top_limit_value_mock.return_value = 0 - get_snapshot_info_mock .return_value = [1, 1, '2010-01-01', 8] + get_circulation_mock = mocker.patch('tokenomics_decentralization.helper.get_circulation_from_entries') + circulation = 5 + get_circulation_mock.return_value = circulation get_metrics_mock.return_value = ['hhi'] - get_metric_value_mock.return_value = [1] + compute_hhi_mock.return_value = 1 + + entries = [(1, ), (2, )] + + hhi_calls = [] - output = analyze_snapshot(None, 'bitcoin', '2010-01-01') + output = analyze_snapshot(entries) + hhi_calls.append(call(entries, circulation)) + assert compute_hhi_mock.call_args_list == hhi_calls assert output == {'hhi': 1} get_clustering_mock.return_value = False @@ -91,132 +46,137 @@ def test_analyze_snapshot(mocker): get_top_limit_type_mock.return_value = 'absolute' get_top_limit_value_mock.return_value = 1 - output = analyze_snapshot(None, 'bitcoin', '2010-01-01') + output = analyze_snapshot(entries) + hhi_calls.append(call(entries[:1], circulation)) + assert compute_hhi_mock.call_args_list == hhi_calls assert output == {'top-1_absolute exclude_below_fees exclude_contracts non-clustered hhi': 1} - get_clustered_entries_mock.return_value = [[4, ], [4, ]] - get_nonclustered_entries_mock.return_value = [[4, ], [4, ]] - - get_force_analyze_mock.return_value = True - - db_insert_mock.return_value = None - db_commit_mock.return_value = None - compute_hhi_mock.return_value = 2 - output = analyze_snapshot(None, 'bitcoin', '2010-01-01') + output = analyze_snapshot(entries) + hhi_calls.append(call(entries[:1], circulation)) + assert compute_hhi_mock.call_args_list == hhi_calls assert output == {'top-1_absolute exclude_below_fees exclude_contracts non-clustered hhi': 2} get_clustering_mock.return_value = True - compute_hhi_mock.return_value = 3 - output = analyze_snapshot(None, 'bitcoin', '2010-01-01') + output = analyze_snapshot(entries) + hhi_calls.append(call(entries[:1], circulation)) + assert compute_hhi_mock.call_args_list == hhi_calls assert output == {'top-1_absolute exclude_below_fees exclude_contracts hhi': 3} get_top_limit_value_mock.return_value = 0 - compute_hhi_mock.return_value = 4 - output = analyze_snapshot(None, 'bitcoin', '2010-01-01') + output = analyze_snapshot(entries) + hhi_calls.append(call(entries, circulation)) + assert compute_hhi_mock.call_args_list == hhi_calls assert output == {'exclude_below_fees exclude_contracts hhi': 4} get_top_limit_type_mock.return_value = 'percentage' get_top_limit_value_mock.return_value = 0.5 - compute_hhi_mock.return_value = 5 - output = analyze_snapshot(None, 'bitcoin', '2010-01-01') + output = analyze_snapshot(entries) + hhi_calls.append(call(entries[:int(len(entries)*0.5)], circulation)) + assert compute_hhi_mock.call_args_list == hhi_calls assert output == {'top-0.5_percentage exclude_below_fees exclude_contracts hhi': 5} get_top_limit_value_mock.return_value = 0 + get_exclude_below_usd_cent_mock.return_value = True + get_exclude_below_fees_mock.return_value = False get_metrics_mock.return_value = ['tau=0.5'] - compute_tau_mock.return_value = [100, None] - output = analyze_snapshot(None, 'bitcoin', '2010-01-01') - assert output == {'exclude_below_fees exclude_contracts tau=0.5': 100} + output = analyze_snapshot(entries) + assert compute_tau_mock.call_args_list == [call(entries, circulation, 0.5)] + assert output == {'exclude_below_usd_cent exclude_contracts tau=0.5': 100} -def test_write_csv_output(mocker): - get_metrics_mock = mocker.patch('tokenomics_decentralization.helper.get_metrics') - get_metrics_mock.return_value = ['hhi'] - - get_output_directories_mock = mocker.patch('tokenomics_decentralization.helper.get_output_directories') - get_output_directories_mock.return_value = [pathlib.Path(__file__).resolve().parent] - - get_clustering_mock = mocker.patch('tokenomics_decentralization.helper.get_clustering_flag') - get_exclude_contracts_mock = mocker.patch('tokenomics_decentralization.helper.get_exclude_contracts_flag') +def test_get_entries(mocker): get_exclude_below_fees_mock = mocker.patch('tokenomics_decentralization.helper.get_exclude_below_fees_flag') - get_exclude_below_usd_cent_mock = mocker.patch('tokenomics_decentralization.helper.get_exclude_below_usd_cent_flag') - get_top_limit_type_mock = mocker.patch('tokenomics_decentralization.helper.get_top_limit_type') - get_top_limit_value_mock = mocker.patch('tokenomics_decentralization.helper.get_top_limit_value') - - get_clustering_mock.return_value = True - get_exclude_contracts_mock.return_value = False get_exclude_below_fees_mock.return_value = False + get_exclude_below_usd_cent_mock = mocker.patch('tokenomics_decentralization.helper.get_exclude_below_usd_cent_flag') get_exclude_below_usd_cent_mock.return_value = False - get_top_limit_type_mock.return_value = 'absolute' - get_top_limit_value_mock.return_value = 0 - write_csv_output([ - ['bitcoin', '2010-01-01', True, False, 'absolute', 0, False, False, 100], - ['ethereum', '2010-01-01', True, False, 'absolute', 0, False, False, 200], - ]) - with open(pathlib.Path(__file__).resolve().parent / 'output.csv') as f: - lines = f.readlines() - assert lines[0] == ','.join(['ledger', 'snapshot_date', 'clustering', 'exclude_contract_addresses', - 'top_limit_type', 'top_limit_value', 'exclude_below_fees', - 'exclude_below_usd_cent', 'hhi']) + '\n' - assert lines[1] == ','.join(['bitcoin', '2010-01-01', 'True', 'False', 'absolute', '0', 'False', 'False', - '100']) + '\n' - assert lines[2] == ','.join(['ethereum', '2010-01-01', 'True', 'False', 'absolute', '0', 'False', 'False', - '200']) + '\n' - os.remove(pathlib.Path(__file__).resolve().parent / 'output.csv') + get_median_tx_fee_mock = mocker.patch('tokenomics_decentralization.helper.get_median_tx_fee') + get_median_tx_fee_mock.return_value = 0 + get_usd_cent_equivalent_mock = mocker.patch('tokenomics_decentralization.helper.get_usd_cent_equivalent') + get_usd_cent_equivalent_mock.return_value = 0 - get_clustering_mock.return_value = False - get_exclude_contracts_mock.return_value = True - get_exclude_below_fees_mock.return_value = True - get_top_limit_type_mock.return_value = 'absolute' - get_top_limit_value_mock.return_value = 10 - - write_csv_output([ - ['bitcoin', '2010-01-01', False, False, 'absolute', 0, False, False, 100], - ['ethereum', '2010-01-01', False, False, 'absolute', 0, False, False, 200], - ]) - with open(pathlib.Path(__file__).resolve().parent / 'output-no_clustering-exclude_contract_addresses-absolute_10-exclude_below_fees.csv') as f: - lines = f.readlines() - assert lines[0] == ','.join(['ledger', 'snapshot_date', 'clustering', 'exclude_contract_addresses', - 'top_limit_type', 'top_limit_value', 'exclude_below_fees', - 'exclude_below_usd_cent', 'hhi']) + '\n' - assert lines[1] == ','.join(['bitcoin', '2010-01-01', 'False', 'False', 'absolute', '0', 'False', 'False', - '100']) + '\n' - assert lines[2] == ','.join(['ethereum', '2010-01-01', 'False', 'False', 'absolute', '0', 'False', 'False', - '200']) + '\n' - os.remove(pathlib.Path(__file__).resolve().parent / 'output-no_clustering-exclude_contract_addresses-absolute_10-exclude_below_fees.csv') + get_db_connector_mock = mocker.patch('tokenomics_decentralization.db_helper.get_connector') + get_db_connector_mock.return_value = 'connector' + + get_special_addresses_mock = mocker.patch('tokenomics_decentralization.helper.get_special_addresses') + get_special_addresses_mock.return_value = set(['addr2']) + + get_db_filename_mock = mocker.patch('tokenomics_decentralization.db_helper.get_db_filename') + get_db_filename_mock.return_value = 'bitcoin_Test.db' + + mocker.patch('builtins.open', mocker.mock_open(read_data='address,balance\naddr1,1\naddr2,2')) + + get_address_entity_mock = mocker.patch('tokenomics_decentralization.db_helper.get_address_entity') + get_address_entity_mock.side_effect = { + 'addr1': 'entity1', + 'addr2': 'entity2', + }.get + + entries = get_entries('bitcoin', '2010-01-01', 'test_filename') + assert entries == [(1,)] + assert get_db_connector_mock.call_args_list == [call('bitcoin_Test.db')] def test_analyze(mocker): - get_output_directories_mock = mocker.patch('tokenomics_decentralization.helper.get_output_directories') - get_output_directories_mock.return_value = [pathlib.Path(__file__).resolve().parent] + get_input_directories_mock = mocker.patch('tokenomics_decentralization.helper.get_input_directories') + get_input_directories_mock.return_value = [pathlib.Path('/').resolve()] is_file_mock = mocker.patch('os.path.isfile') - is_file_mock.return_value = True - - get_db_connector_mock = mocker.patch('tokenomics_decentralization.analyze.get_connector') + is_file_mock.side_effect = { + pathlib.Path('/bitcoin_2010-01-01_raw_data.csv').resolve(): True, + pathlib.Path('/bitcoin_2011-01-01_raw_data.csv').resolve(): False, + pathlib.Path('/ethereum_2010-01-01_raw_data.csv').resolve(): False, + pathlib.Path('/ethereum_2011-01-01_raw_data.csv').resolve(): True, + }.get + + get_db_connector_mock = mocker.patch('tokenomics_decentralization.db_helper.get_connector') get_db_connector_mock.return_value = 'connector' + get_entries_mock = mocker.patch('tokenomics_decentralization.analyze.get_entries') + entries = [(1, ), (2, )] + get_entries_mock.return_value = entries + analyze_snapshot_mock = mocker.patch('tokenomics_decentralization.analyze.analyze_snapshot') analyze_snapshot_mock.return_value = {'hhi': 1} - write_csv_output_mock = mocker.patch('tokenomics_decentralization.analyze.write_csv_output') - write_csv_output_mock.return_value = None - - get_output_row_mock = mocker.patch('tokenomics_decentralization.analyze.get_output_row') + get_output_row_mock = mocker.patch('tokenomics_decentralization.helper.get_output_row') get_output_row_mock.return_value = 'row' - output_rows = analyze(['bitcoin'], ['2010-01-01']) - assert output_rows == ['row'] - analyze_snapshot_mock.assert_called_with('connector', 'bitcoin', '2010-01-01') - - output_rows = analyze(['bitcoin', 'ethereum'], ['2010-01-01', '2011-01-01']) - assert output_rows == ['row', 'row', 'row', 'row'] - - is_file_mock.return_value = False - output_rows = analyze(['bitcoin', 'ethereum'], ['2010-01-01', '2011-01-01']) - assert output_rows == [] + write_csv_output_mock = mocker.patch('tokenomics_decentralization.helper.write_csv_output') + + get_input_dirs_calls = [] + get_entries_calls = [] + analyze_snapshot_calls = [] + get_row_calls = [] + write_output_calls = [] + + analyze(['bitcoin'], ['2010-01-01']) + get_input_dirs_calls.append(call()) + assert get_input_directories_mock.call_args_list == get_input_dirs_calls + get_entries_calls.append(call('bitcoin', '2010-01-01', pathlib.Path('/bitcoin_2010-01-01_raw_data.csv').resolve())) + assert get_entries_mock.call_args_list == get_entries_calls + analyze_snapshot_calls.append(call(entries)) + assert analyze_snapshot_mock.call_args_list == analyze_snapshot_calls + get_row_calls.append(call('bitcoin', '2010-01-01', {'hhi': 1})) + assert get_output_row_mock.call_args_list == get_row_calls + write_output_calls.append(call(['row'])) + assert write_csv_output_mock.call_args_list == write_output_calls + + analyze(['bitcoin', 'ethereum'], ['2010-01-01', '2011-01-01']) + get_input_dirs_calls += 4 * [call()] + assert get_input_directories_mock.call_args_list == get_input_dirs_calls + get_entries_calls.append(call('bitcoin', '2010-01-01', pathlib.Path('/bitcoin_2010-01-01_raw_data.csv').resolve())) + get_entries_calls.append(call('ethereum', '2011-01-01', pathlib.Path('/ethereum_2011-01-01_raw_data.csv').resolve())) + assert get_entries_mock.call_args_list == get_entries_calls + analyze_snapshot_calls += 2 * [call(entries)] + assert analyze_snapshot_mock.call_args_list == analyze_snapshot_calls + get_row_calls.append(call('bitcoin', '2010-01-01', {'hhi': 1})) + get_row_calls.append(call('ethereum', '2011-01-01', {'hhi': 1})) + assert get_output_row_mock.call_args_list == get_row_calls + write_output_calls.append(call(['row', 'row'])) + assert write_csv_output_mock.call_args_list == write_output_calls diff --git a/tests/test_db_helper.py b/tests/test_db_helper.py new file mode 100644 index 0000000..eb8e750 --- /dev/null +++ b/tests/test_db_helper.py @@ -0,0 +1,68 @@ +import tokenomics_decentralization.helper as hlp +import tokenomics_decentralization.db_helper as db_hlp +import sqlite3 +import os +import pytest + + +@pytest.fixture +def setup_and_cleanup(): + """ + This function can be used to set up the right conditions for a test and also clean up after the test is finished. + The part before the yield command is run before the test (setup) and the part after the yield command is run + after (cleanup) + """ + # Set up + db_filename = db_hlp.get_db_filename('test') + + yield + + # Clean up + os.remove(db_filename) + + +def test_get_db_filename(mocker): + get_active_source_keywords_mock = mocker.patch("tokenomics_decentralization.helper.get_active_source_keywords") + get_active_source_keywords_mock.return_value = ['test'] + + filename = '_'.join(['bitcoin'] + ['test']) + db_filename = db_hlp.get_db_filename('bitcoin') + assert db_filename == hlp.MAPPING_INFO_DIR / f'addresses/{filename}.db' + + +def test_get_connector(setup_and_cleanup): + db_filename = db_hlp.get_db_filename('test') + conn = db_hlp.get_connector(db_filename) + assert type(conn) is sqlite3.Connection + + +def test_insert_mapping(setup_and_cleanup): + db_filename = db_hlp.get_db_filename('test') + conn = db_hlp.get_connector(db_filename) + c = conn.cursor() + + db_hlp.insert_mapping(conn, 'a1', 'e1', False) + db_hlp.commit_database(conn) + entry = c.execute('SELECT address, entity, is_contract FROM mapping WHERE address=?', ('a1', )).fetchone() + assert entry == ('a1', 'e1', 0) + + db_hlp.insert_mapping(conn, 'a1', 'e1', False) + db_hlp.commit_database(conn) + entry = c.execute('SELECT address, entity, is_contract FROM mapping WHERE address=?', ('a1', )).fetchone() + assert entry == ('a1', 'e1', 0) + + with pytest.raises(ValueError): + db_hlp.insert_mapping(conn, 'a1', 'e2', False) + + +def test_get_address_entity(setup_and_cleanup): + db_filename = db_hlp.get_db_filename('test') + conn = db_hlp.get_connector(db_filename) + db_hlp.insert_mapping(conn, 'a1', 'e1', False) + db_hlp.commit_database(conn) + + entity = db_hlp.get_address_entity(conn, 'a1') + assert entity == 'e1' + + entity = db_hlp.get_address_entity(conn, 'blah') + assert entity == 'blah' diff --git a/tests/test_helper.py b/tests/test_helper.py index fbde5fb..347f2a0 100644 --- a/tests/test_helper.py +++ b/tests/test_helper.py @@ -1,9 +1,9 @@ +import tokenomics_decentralization.helper as hlp +import pathlib +import os import argparse import datetime -import os import pytest -import tokenomics_decentralization.helper as hlp -import pathlib def test_valid_date(): @@ -43,6 +43,25 @@ def test_get_snapshot_dates(): assert len(snapshot_dates) > 0 +def test_increment_date(): + date = datetime.datetime.strptime('2021-01-01', '%Y-%m-%d').date() + + new_date = hlp.increment_date(date, 'day') + assert new_date == datetime.date(2021, 1, 2) + + new_date = hlp.increment_date(date, 'week') + assert new_date == datetime.date(2021, 1, 8) + + new_date = hlp.increment_date(date, 'month') + assert new_date == datetime.date(2021, 2, 1) + + new_date = hlp.increment_date(date, 'year') + assert new_date == datetime.date(2022, 1, 1) + + with pytest.raises(ValueError): + hlp.increment_date(date, 'other') + + def test_input_directories(): input_dirs = hlp.get_input_directories() assert isinstance(input_dirs, list) @@ -118,6 +137,10 @@ def test_get_dates_between(): def test_get_median_tx_fee(mocker): get_granularity_mock = mocker.patch("tokenomics_decentralization.helper.get_granularity") + get_granularity_mock.return_value = 'year' + fee1 = hlp.get_median_tx_fee('bitcoin', '2023-10-18') + assert fee1 == 3347 + get_granularity_mock.return_value = 'month' fee1 = hlp.get_median_tx_fee('bitcoin', '2023-10-18') assert fee1 == 2820 @@ -128,16 +151,33 @@ def test_get_median_tx_fee(mocker): fee3 = hlp.get_median_tx_fee('bitcoin', '2023-10-18') assert fee3 == 2712 + get_granularity_mock.return_value = 'blahblah' + fee4 = hlp.get_median_tx_fee('bitcoin', '2023-10-18') + assert fee4 == 0 + + get_granularity_mock.return_value = 'week' + fee5 = hlp.get_median_tx_fee('bitcoin', '3333-10-18') + assert fee5 == 0 + + +def test_get_denomination_from_coin(): + assert hlp.get_denomination_from_coin('bitcoin') == 1e8 + assert hlp.get_denomination_from_coin('bitcoin_cash') == 1e8 + assert hlp.get_denomination_from_coin('cardano') == 1e6 + assert hlp.get_denomination_from_coin('ethereum') == 1e9 + assert hlp.get_denomination_from_coin('litecoin') == 1e8 + assert hlp.get_denomination_from_coin('tezos') == 1e6 + assert hlp.get_denomination_from_coin('blahblah') == 1 + def test_config_flags(mocker): functions_to_test = [ hlp.get_plot_flag, hlp.get_force_map_addresses_flag, - hlp.get_force_map_balances_flag, - hlp.get_force_analyze_flag, hlp.get_clustering_flag, hlp.get_exclude_contracts_flag, hlp.get_exclude_below_fees_flag, + hlp.get_exclude_below_usd_cent_flag, ] for function in functions_to_test: @@ -151,6 +191,16 @@ def test_config_flags(mocker): with pytest.raises(ValueError): function() + get_active_source_keywords_mock = mocker.patch("tokenomics_decentralization.helper.get_active_source_keywords") + + get_active_source_keywords_mock.return_value = ['test'] + clustering_flag = hlp.get_clustering_flag() + assert clustering_flag is True + + get_active_source_keywords_mock.return_value = [] + clustering_flag = hlp.get_clustering_flag() + assert clustering_flag is False + def test_get_metrics(mocker): metrics = hlp.get_metrics() @@ -280,3 +330,151 @@ def test_get_usd_cent_equivalent(mocker): assert balance_threshold == 1e7 os.remove(hlp.PRICE_DATA_DIR / 'test-USD.csv') + + balance_threshold = hlp.get_usd_cent_equivalent(ledger='test', date='2021-03-01') + assert balance_threshold == 0 + + balance_threshold = hlp.get_usd_cent_equivalent(ledger='bitcoin', date='3333-03-01') + assert balance_threshold == 0 + + +def test_get_output_row(mocker): + get_metrics_mock = mocker.patch('tokenomics_decentralization.helper.get_metrics') + get_metrics_mock.return_value = ['hhi', 'gini'] + + get_clustering_mock = mocker.patch('tokenomics_decentralization.helper.get_clustering_flag') + get_exclude_contracts_mock = mocker.patch('tokenomics_decentralization.helper.get_exclude_contracts_flag') + get_exclude_below_fees_mock = mocker.patch('tokenomics_decentralization.helper.get_exclude_below_fees_flag') + get_exclude_below_usd_cent_mock = mocker.patch('tokenomics_decentralization.helper.get_exclude_below_usd_cent_flag') + get_top_limit_type_mock = mocker.patch('tokenomics_decentralization.helper.get_top_limit_type') + get_top_limit_value_mock = mocker.patch('tokenomics_decentralization.helper.get_top_limit_value') + + get_clustering_mock.return_value = True + get_exclude_contracts_mock.return_value = False + get_exclude_below_fees_mock.return_value = False + get_exclude_below_usd_cent_mock.return_value = False + get_top_limit_type_mock.return_value = 'absolute' + get_top_limit_value_mock.return_value = 0 + + metrics = {'hhi': 1, 'gini': 0} + csv_row = hlp.get_output_row('bitcoin', '2010-01-01', metrics) + assert csv_row == ['bitcoin', '2010-01-01', True, False, 'absolute', 0, False, False, 1, 0] + + get_clustering_mock.return_value = False + metrics = {'non-clustered hhi': 1, 'non-clustered gini': 0} + csv_row = hlp.get_output_row('bitcoin', '2010-01-01', metrics) + assert csv_row == ['bitcoin', '2010-01-01', False, False, 'absolute', 0, False, False, 1, 0] + + get_exclude_contracts_mock.return_value = True + metrics = {'exclude_contracts non-clustered hhi': 1, 'exclude_contracts non-clustered gini': 0} + csv_row = hlp.get_output_row('bitcoin', '2010-01-01', metrics) + assert csv_row == ['bitcoin', '2010-01-01', False, True, 'absolute', 0, False, False, 1, 0] + + get_top_limit_value_mock.return_value = 1 + metrics = {'top-1_absolute exclude_contracts non-clustered hhi': 1, 'top-1_absolute exclude_contracts non-clustered gini': 0} + csv_row = hlp.get_output_row('bitcoin', '2010-01-01', metrics) + assert csv_row == ['bitcoin', '2010-01-01', False, True, 'absolute', 1, False, False, 1, 0] + + get_exclude_below_fees_mock.return_value = True + get_top_limit_value_mock.return_value = 1 + metrics = {'top-1_absolute exclude_below_fees exclude_contracts non-clustered hhi': 1, 'top-1_absolute exclude_below_fees exclude_contracts non-clustered gini': 0} + csv_row = hlp.get_output_row('bitcoin', '2010-01-01', metrics) + assert csv_row == ['bitcoin', '2010-01-01', False, True, 'absolute', 1, True, False, 1, 0] + + get_exclude_below_fees_mock.return_value = False + get_exclude_below_usd_cent_mock.return_value = True + metrics = {'top-1_absolute exclude_below_usd_cent exclude_contracts non-clustered hhi': 1, 'top-1_absolute exclude_below_usd_cent exclude_contracts non-clustered gini': 0} + csv_row = hlp.get_output_row('bitcoin', '2010-01-01', metrics) + assert csv_row == ['bitcoin', '2010-01-01', False, True, 'absolute', 1, False, True, 1, 0] + + +def test_write_csv_output(mocker): + get_metrics_mock = mocker.patch('tokenomics_decentralization.helper.get_metrics') + get_metrics_mock.return_value = ['hhi'] + + get_output_directories_mock = mocker.patch('tokenomics_decentralization.helper.get_output_directories') + get_output_directories_mock.return_value = [pathlib.Path(__file__).resolve().parent] + + get_clustering_mock = mocker.patch('tokenomics_decentralization.helper.get_clustering_flag') + get_exclude_contracts_mock = mocker.patch('tokenomics_decentralization.helper.get_exclude_contracts_flag') + get_exclude_below_fees_mock = mocker.patch('tokenomics_decentralization.helper.get_exclude_below_fees_flag') + get_exclude_below_usd_cent_mock = mocker.patch('tokenomics_decentralization.helper.get_exclude_below_usd_cent_flag') + get_top_limit_type_mock = mocker.patch('tokenomics_decentralization.helper.get_top_limit_type') + get_top_limit_value_mock = mocker.patch('tokenomics_decentralization.helper.get_top_limit_value') + + get_clustering_mock.return_value = True + get_exclude_contracts_mock.return_value = False + get_exclude_below_fees_mock.return_value = False + get_exclude_below_usd_cent_mock.return_value = False + get_top_limit_type_mock.return_value = 'absolute' + get_top_limit_value_mock.return_value = 0 + + hlp.write_csv_output([ + ['bitcoin', '2010-01-01', True, False, 'absolute', 0, False, False, 100], + ['ethereum', '2010-01-01', True, False, 'absolute', 0, False, False, 200], + ]) + with open(pathlib.Path(__file__).resolve().parent / 'output.csv') as f: + lines = f.readlines() + assert lines[0] == ','.join(['ledger', 'snapshot_date', 'clustering', 'exclude_contract_addresses', + 'top_limit_type', 'top_limit_value', 'exclude_below_fees', + 'exclude_below_usd_cent', 'hhi']) + '\n' + assert lines[1] == ','.join(['bitcoin', '2010-01-01', 'True', 'False', 'absolute', '0', 'False', 'False', + '100']) + '\n' + assert lines[2] == ','.join(['ethereum', '2010-01-01', 'True', 'False', 'absolute', '0', 'False', 'False', + '200']) + '\n' + os.remove(pathlib.Path(__file__).resolve().parent / 'output.csv') + + get_clustering_mock.return_value = False + get_exclude_contracts_mock.return_value = True + get_exclude_below_fees_mock.return_value = True + get_exclude_below_usd_cent_mock.return_value = True + get_top_limit_type_mock.return_value = 'absolute' + get_top_limit_value_mock.return_value = 10 + + hlp.write_csv_output([ + ['bitcoin', '2010-01-01', False, False, 'absolute', 0, False, False, 100], + ['ethereum', '2010-01-01', False, False, 'absolute', 0, False, False, 200], + ]) + with open(pathlib.Path(__file__).resolve().parent / 'output-no_clustering-exclude_contract_addresses-absolute_10-exclude_below_fees-exclude_below_usd_cent.csv') as f: + lines = f.readlines() + assert lines[0] == ','.join(['ledger', 'snapshot_date', 'clustering', 'exclude_contract_addresses', + 'top_limit_type', 'top_limit_value', 'exclude_below_fees', + 'exclude_below_usd_cent', 'hhi']) + '\n' + assert lines[1] == ','.join(['bitcoin', '2010-01-01', 'False', 'False', 'absolute', '0', 'False', 'False', + '100']) + '\n' + assert lines[2] == ','.join(['ethereum', '2010-01-01', 'False', 'False', 'absolute', '0', 'False', 'False', + '200']) + '\n' + os.remove(pathlib.Path(__file__).resolve().parent / 'output-no_clustering-exclude_contract_addresses-absolute_10-exclude_below_fees-exclude_below_usd_cent.csv') + + +def test_get_active_source_keywords(mocker): + get_config_mock = mocker.patch("tokenomics_decentralization.helper.get_config_data") + get_config_mock.return_value = {'analyze_flags': {'clustering_sources': ['test']}} + + active_sources = hlp.get_active_source_keywords() + assert active_sources == ['test'] + + +def test_get_active_sources(mocker): + json_load_mock = mocker.patch('json.load') + json_load_mock.return_value = {'Test 1': ['test1', 'test11'], 'Test 2': ['test2']} + + get_active_source_keywords_mock = mocker.patch("tokenomics_decentralization.helper.get_active_source_keywords") + get_active_source_keywords_mock.return_value = ['Test 1'] + + active_sources = hlp.get_active_sources() + assert active_sources == set(['test1', 'test11']) + + +def test_get_clusters(mocker): + mocker.patch('builtins.open', mocker.mock_open( + read_data='{"name": "entity1", "address": "addr1", "source": "test"}\n{"name": "entity2", "address": "addr2", "source": "test"}\n{"name": "entity3", "address": "addr2", "source": "test2"}\n{"name": "entity3", "address": "addr1", "source": "test2"}\n{"name": "entity4", "address": "addr4", "source": "test"}\n{"name": "entity5", "address": "addr4", "source": "test2"}\n{"name": "entity6", "address": "addr6", "source": "test"}\n{"name": "entity7", "address": "addr1", "source": "test3"}' + )) + + get_active_sources_mock = mocker.patch("tokenomics_decentralization.helper.get_active_sources") + get_active_sources_mock.return_value = ['test', 'test2'] + + clusters = hlp.get_clusters('bitcoin') + assert clusters['entity1'] == clusters['entity2'] + assert clusters['entity1'] == clusters['entity3'] + assert clusters['entity4'] == clusters['entity5'] diff --git a/tests/test_map.py b/tests/test_map.py index 5e4ba50..224239a 100644 --- a/tests/test_map.py +++ b/tests/test_map.py @@ -1,132 +1,96 @@ -from tokenomics_decentralization.map import fill_db_with_addresses, apply_mapping, fill_db_with_balances +from tokenomics_decentralization.map import apply_mapping from unittest.mock import call -import pathlib - - -def test_fill_db_with_addresses(mocker): - db_insert_ledger_mock = mocker.patch('tokenomics_decentralization.db_helper.insert_ledger') - db_insert_ledger_mock.return_value = None - db_insert_entity_mock = mocker.patch('tokenomics_decentralization.db_helper.insert_entity') - db_insert_entity_mock.return_value = None - db_insert_update_address_mock = mocker.patch('tokenomics_decentralization.db_helper.insert_update_address') - db_insert_update_address_mock.return_value = None - db_commit_mock = mocker.patch('tokenomics_decentralization.db_helper.commit_database') - db_commit_mock.return_value = None - - json_load_mock = mocker.patch('json.loads') - - fill_db_with_addresses('connector', 'random ledger') - assert json_load_mock.call_args_list == [] - assert db_insert_entity_mock.call_args_list == [] - assert db_insert_update_address_mock.call_args_list == [] - assert db_commit_mock.call_args_list == [] - - json_load_mock.return_value = {'address': 'address 1', 'name': 'Entity Name 1', 'source': 'website'} - fill_db_with_addresses('connector', 'bitcoin') - db_insert_ledger_mock.assert_called_with('connector', 'bitcoin') - assert call('connector', 'bitcoin', 'Entity Name 1') in db_insert_entity_mock.call_args_list - assert call('connector', 'bitcoin', 'address 1', 'Entity Name 1', False) in db_insert_update_address_mock.call_args_list - db_commit_mock.assert_called() - - -def test_fill_db_with_balances(mocker): - get_input_directories_mock = mocker.patch('tokenomics_decentralization.helper.get_input_directories') - get_input_directories_mock.return_value = [pathlib.Path('/input').resolve()] - - os_isfile_mock = mocker.patch('os.path.isfile') - os_isfile_mock.return_value = True - - db_insert_address_without_update_mock = mocker.patch('tokenomics_decentralization.db_helper.insert_address_without_update') - db_insert_snapshot_mock = mocker.patch('tokenomics_decentralization.db_helper.insert_snapshot') - db_insert_balance_mock = mocker.patch('tokenomics_decentralization.db_helper.insert_balance') - db_update_circulation_mock = mocker.patch('tokenomics_decentralization.db_helper.update_circulation') - db_commit_mock = mocker.patch('tokenomics_decentralization.db_helper.commit_database') - - mocker.patch('builtins.open', mocker.mock_open(read_data='address,type,balance\naddr1,pubkey,100\naddr2,pubkey,200')) - - fill_db_with_balances('connector', 'bitcoin', '2010-01-01') - assert db_insert_snapshot_mock.call_args_list == [call('connector', 'bitcoin', '2010-01-01')] - assert db_insert_address_without_update_mock.call_args_list == [call('connector', 'bitcoin', 'addr1'), call('connector', 'bitcoin', 'addr2')] - assert db_insert_balance_mock.call_args_list == [call('connector', 'bitcoin', '2010-01-01', 'addr1', 100), call('connector', 'bitcoin', '2010-01-01', 'addr2', 200)] - assert db_update_circulation_mock.call_args_list == [call('connector', 'bitcoin', '2010-01-01', 300)] - assert db_commit_mock.call_args_list == [call('connector')] - - db_insert_address_without_update_mock.reset_mock() - db_insert_snapshot_mock.reset_mock() - db_insert_balance_mock.reset_mock() - db_update_circulation_mock.reset_mock() - db_commit_mock.reset_mock() - - mocker.patch('builtins.open', mocker.mock_open(read_data='address,balance\naddr1,0\naddr2,1000000000')) - - fill_db_with_balances('connector', 'ethereum', '2010-01-01') - assert db_insert_snapshot_mock.call_args_list == [call('connector', 'ethereum', '2010-01-01')] - assert db_insert_address_without_update_mock.call_args_list == [call('connector', 'ethereum', 'addr2')] - assert db_insert_balance_mock.call_args_list == [call('connector', 'ethereum', '2010-01-01', 'addr2', 1)] - assert db_update_circulation_mock.call_args_list == [call('connector', 'ethereum', '2010-01-01', 1)] - assert db_commit_mock.call_args_list == [call('connector')] def test_apply_mapping(mocker): - get_db_connector_mock = mocker.patch('tokenomics_decentralization.map.get_connector') + get_db_connector_mock = mocker.patch('tokenomics_decentralization.db_helper.get_connector') get_db_connector_mock.return_value = 'connector' + commit_database_mock = mocker.patch('tokenomics_decentralization.db_helper.commit_database') - fill_db_with_addresses_mock = mocker.patch('tokenomics_decentralization.map.fill_db_with_addresses') - fill_db_with_balances_mock = mocker.patch('tokenomics_decentralization.map.fill_db_with_balances') + insert_mapping_mock = mocker.patch('tokenomics_decentralization.db_helper.insert_mapping') - get_force_map_addresses_mock = mocker.patch('tokenomics_decentralization.helper.get_force_map_addresses_flag') - get_force_map_balances_mock = mocker.patch('tokenomics_decentralization.helper.get_force_map_balances_flag') + get_clusters_mock = mocker.patch('tokenomics_decentralization.helper.get_clusters') + get_clusters_mock.return_value = {'entity2': 'cluster'} + + get_active_sources_mock = mocker.patch('tokenomics_decentralization.helper.get_active_sources') + get_active_sources_mock.return_value = ['test'] - get_output_directories_mock = mocker.patch('tokenomics_decentralization.helper.get_output_directories') - get_input_directories_mock = mocker.patch('tokenomics_decentralization.helper.get_input_directories') + get_db_filename_mock = mocker.patch('tokenomics_decentralization.db_helper.get_db_filename') + db_filename = 'bitcoin_Test.db' + get_db_filename_mock.return_value = db_filename os_isfile_mock = mocker.patch('os.path.isfile') os_isfile_mock.side_effect = { - pathlib.Path('/output').resolve() / 'bitcoin_2010-01-01.db': False, - pathlib.Path('/input1').resolve() / 'bitcoin_2010-01-01_raw_data.csv': False, - pathlib.Path('/input2').resolve() / 'bitcoin_2010-01-01_raw_data.csv': False, + db_filename: True }.get + get_force_map_addresses_mock = mocker.patch('tokenomics_decentralization.helper.get_force_map_addresses_flag') + get_force_map_addresses_mock.return_value = False - get_force_map_balances_mock.return_value = False - get_output_directories_mock.return_value = [pathlib.Path('/output').resolve()] - get_input_directories_mock.return_value = [pathlib.Path('/input1').resolve(), pathlib.Path('/input2').resolve()] + get_db_connector_calls = [] + insert_mapping_calls = [] + commit_db_calls = [] - apply_mapping('bitcoin', '2010-01-01') - assert get_db_connector_mock.call_args_list == [] - assert fill_db_with_addresses_mock.call_args_list == [] - assert fill_db_with_balances_mock.call_args_list == [] + apply_mapping('bitcoin') + assert get_db_connector_mock.call_args_list == get_db_connector_calls os_isfile_mock.side_effect = { - pathlib.Path('/output').resolve() / 'bitcoin_2010-01-01.db': True, + db_filename: False }.get - get_input_directories_mock.reset_mock() - apply_mapping('bitcoin', '2010-01-01') - assert get_input_directories_mock.call_args_list == [] - assert fill_db_with_addresses_mock.call_args_list == [] - assert fill_db_with_balances_mock.call_args_list == [] + # Test normal mapping insertion + mocker.patch('builtins.open', mocker.mock_open(read_data='{"name": "entity1", "address": "addr1", "source": "test"}')) - get_force_map_addresses_mock.return_value = True - get_force_map_balances_mock.return_value = True + apply_mapping('bitcoin') + get_db_connector_calls.append(call(db_filename)) + assert get_db_connector_mock.call_args_list == get_db_connector_calls + insert_mapping_calls.append(call('connector', 'addr1', 'entity1', False)) + assert insert_mapping_mock.call_args_list == insert_mapping_calls + commit_db_calls.append(call('connector')) + assert commit_database_mock.call_args_list == commit_db_calls + + # Test ignoring non-active source + mocker.patch('builtins.open', mocker.mock_open(read_data='{"name": "entity1", "address": "addr1", "source": "other"}')) - apply_mapping('bitcoin', '2010-01-01') - assert fill_db_with_addresses_mock.call_args_list == [call('connector', 'bitcoin')] - assert fill_db_with_balances_mock.call_args_list == [call('connector', 'bitcoin', '2010-01-01')] + apply_mapping('bitcoin') + get_db_connector_calls.append(call(db_filename)) + assert get_db_connector_mock.call_args_list == get_db_connector_calls + assert insert_mapping_mock.call_args_list == insert_mapping_calls + commit_db_calls.append(call('connector')) + assert commit_database_mock.call_args_list == commit_db_calls + # Test force map flag os_isfile_mock.side_effect = { - pathlib.Path('/output').resolve() / 'bitcoin_2010-01-01.db': False, - pathlib.Path('/input1').resolve() / 'bitcoin_2010-01-01_raw_data.csv': True + db_filename: True }.get - get_force_map_addresses_mock.return_value = False - get_force_map_balances_mock.return_value = False - get_input_directories_mock.reset_mock() - fill_db_with_addresses_mock.reset_mock() - fill_db_with_balances_mock.reset_mock() - - apply_mapping('bitcoin', '2010-01-01') - assert get_input_directories_mock.call_args_list == [call()] - assert fill_db_with_addresses_mock.call_args_list == [call('connector', 'bitcoin')] - assert fill_db_with_balances_mock.call_args_list == [call('connector', 'bitcoin', '2010-01-01')] + get_force_map_addresses_mock.return_value = True + + apply_mapping('bitcoin') + get_db_connector_calls.append(call(db_filename)) + assert get_db_connector_mock.call_args_list == get_db_connector_calls + commit_db_calls.append(call('connector')) + assert commit_database_mock.call_args_list == commit_db_calls + + # Test cluster + mocker.patch('builtins.open', mocker.mock_open(read_data='{"name": "entity2", "address": "addr1", "source": "test"}')) + + apply_mapping('bitcoin') + get_db_connector_calls.append(call(db_filename)) + assert get_db_connector_mock.call_args_list == get_db_connector_calls + insert_mapping_calls.append(call('connector', 'addr1', 'cluster', False)) + assert insert_mapping_mock.call_args_list == insert_mapping_calls + commit_db_calls.append(call('connector')) + assert commit_database_mock.call_args_list == commit_db_calls + + # Test is_contract + mocker.patch('builtins.open', mocker.mock_open(read_data='{"name": "entity2", "address": "addr1", "source": "test", "is_contract": true}')) + + apply_mapping('bitcoin') + get_db_connector_calls.append(call(db_filename)) + assert get_db_connector_mock.call_args_list == get_db_connector_calls + insert_mapping_calls.append(call('connector', 'addr1', 'cluster', True)) + assert insert_mapping_mock.call_args_list == insert_mapping_calls + commit_db_calls.append(call('connector')) + assert commit_database_mock.call_args_list == commit_db_calls diff --git a/tests/test_run.py b/tests/test_run.py index 8af431b..9ebdf00 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -10,17 +10,21 @@ def test_run(mocker): get_plot_flag_mock.return_value = False - run.main(['bitcoin'], ['2010-01-01']) + mapping_calls = [] + analyze_calls = [] - apply_mapping_mock.assert_called_with('bitcoin', '2010-01-01') - analyze_mock.assert_called_with(['bitcoin'], ['2010-01-01']) + run.main(['bitcoin'], ['2010-01-01']) + mapping_calls.append(call('bitcoin')) + assert apply_mapping_mock.call_args_list == mapping_calls + analyze_calls.append(call(['bitcoin'], ['2010-01-01'])) + assert analyze_mock.call_args_list == analyze_calls run.main(['bitcoin', 'ethereum'], ['2010-01-01', '2020-01-01']) - assert call('bitcoin', '2010-01-01') in apply_mapping_mock.call_args_list - assert call('bitcoin', '2020-01-01') in apply_mapping_mock.call_args_list - assert call('ethereum', '2010-01-01') in apply_mapping_mock.call_args_list - assert call('ethereum', '2020-01-01') in apply_mapping_mock.call_args_list - analyze_mock.assert_called_with(['bitcoin', 'ethereum'], ['2010-01-01', '2020-01-01']) + mapping_calls.append(call('bitcoin')) + mapping_calls.append(call('ethereum')) + assert apply_mapping_mock.call_args_list == mapping_calls + analyze_calls.append(call(['bitcoin', 'ethereum'], ['2010-01-01', '2020-01-01'])) + assert analyze_mock.call_args_list == analyze_calls get_plot_flag_mock.return_value = True run.main(['bitcoin'], ['2010-01-01'])