def __init__( self, trades_number: int, seconds_between_trades: int, seconds_between_balance_save: int, rotkehlchen, fake_kraken, fake_binance, ): self.seconds_between_trades = seconds_between_trades self.seconds_between_balance_save = seconds_between_balance_save self.trades_number = trades_number self.current_ts = STARTING_TIMESTAMP self.last_trade_ts = 0 self.last_balance_save_ts = 0 self.funds = STARTING_FUNDS self.rotki = rotkehlchen self.kraken = fake_kraken self.binance = fake_binance timestamp, _, _ = self.get_next_ts() for asset, value in self.funds.items(): if asset.is_fiat(): self.rotki.data.db.add_manually_tracked_balances([ManuallyTrackedBalance( asset=asset, label=f'{asset.identifier} balance', amount=value, location=Location.BANKS, tags=None, balance_type=BalanceType.ASSET, )]) self.rotki.query_balances(requested_save_data=True, timestamp=timestamp) # divide our starting funds between exchanges and keep a part out divide_by = len(ALLOWED_EXCHANGES) + 1 for asset, value in self.funds.items(): amount = value / divide_by for exchange in ALLOWED_EXCHANGES: timestamp, _, _ = self.get_next_ts() skip_exchange = asset.is_fiat() and exchange != 'kraken' if not skip_exchange: getattr(self, exchange).deposit( asset=asset, amount=amount, time=timestamp, ) if asset.is_fiat(): self.rotki.data.db.add_manually_tracked_balances([ManuallyTrackedBalance( asset=asset, label=f'{asset.identifier} balance {timestamp}', amount=value, location=Location.BANKS, tags=None, balance_type=BalanceType.ASSET, )]) self.rotki.query_balances(requested_save_data=True, timestamp=timestamp) self.last_balance_save_ts = timestamp
def test_deserialize_location(database): balances = [] for idx, data in enumerate(Location): assert deserialize_location(str(data)) == data balances.append( ManuallyTrackedBalance( asset=A_BTC, label='Test' + str(idx), amount=FVal(1), location=data, tags=None, )) with pytest.raises(DeserializationError): deserialize_location('dsadsad') with pytest.raises(DeserializationError): deserialize_location(15) # Also write and read each location to DB to make sure that # location.serialize_for_db() and deserialize_location_from_db work fine add_manually_tracked_balances(database, balances) balances = database.get_manually_tracked_balances() for data in Location: assert data in (x.location for x in balances)
def test_custom_tokens_delete_guard(rotkehlchen_api_server): """Test that deleting an owned ethereum token is guarded against""" user_db = rotkehlchen_api_server.rest_api.rotkehlchen.data.db token0_id = ETHEREUM_DIRECTIVE + INITIAL_TOKENS[0].ethereum_address user_db.add_manually_tracked_balances([ManuallyTrackedBalance( asset=Asset(token0_id), label='manual1', amount=FVal(1), location=Location.EXTERNAL, tags=None, balance_type=BalanceType.ASSET, )]) # Try to delete the token and see it fails because a user owns it response = requests.delete( api_url_for( rotkehlchen_api_server, 'ethereumassetsresource', ), json={'address': INITIAL_TOKENS[0].ethereum_address}, ) expected_msg = 'Failed to delete asset with id' assert_error_response( response=response, contained_in_msg=expected_msg, status_code=HTTPStatus.CONFLICT, )
def test_query_owned_assets( rotkehlchen_api_server_with_exchanges, ethereum_accounts, btc_accounts, ): """Test that using the query all owned assets endpoint works""" # Disable caching of query results rotki = rotkehlchen_api_server_with_exchanges.rest_api.rotkehlchen rotki.chain_manager.cache_ttl_secs = 0 setup = setup_balances( rotki=rotki, ethereum_accounts=ethereum_accounts, btc_accounts=btc_accounts, manually_tracked_balances=[ ManuallyTrackedBalance( asset=A_EUR, label='My EUR bank', amount=FVal('1550'), location=Location.BANKS, tags=None, ) ], ) # Get all our mocked balances and save them in the DB with ExitStack() as stack: setup.enter_all_patches(stack) response = requests.get( api_url_for( rotkehlchen_api_server_with_exchanges, "allbalancesresource", ), json={'save_data': True}, ) assert_proper_response(response) # And now check that the query owned assets endpoint works with ExitStack() as stack: setup.enter_all_patches(stack) response = requests.get( api_url_for( rotkehlchen_api_server_with_exchanges, "ownedassetsresource", ), ) assert_proper_response(response) data = response.json() assert data['message'] == '' assert set(data['result']) == {'ETH', 'BTC', 'EUR', 'RDN'}
def test_custom_asset_delete_guard(rotkehlchen_api_server): """Test that deleting an owned asset is guarded against""" user_db = rotkehlchen_api_server.rest_api.rotkehlchen.data.db custom1 = { 'asset_type': 'own chain', 'name': 'foo token', 'symbol': 'FOO', 'started': 5, } response = requests.put( api_url_for( rotkehlchen_api_server, 'allassetsresource', ), json=custom1, ) result = assert_proper_response_with_result(response) custom1_id = result['identifier'] user_db.add_manually_tracked_balances([ ManuallyTrackedBalance( id=-1, asset=Asset(custom1_id), label='manual1', amount=FVal(1), location=Location.EXTERNAL, tags=None, balance_type=BalanceType.ASSET, ) ]) # Try to delete the asset and see it fails because a user owns it response = requests.delete( api_url_for( rotkehlchen_api_server, 'allassetsresource', ), json={'identifier': custom1_id}, ) expected_msg = 'Failed to delete asset with id' assert_error_response( response=response, contained_in_msg=expected_msg, status_code=HTTPStatus.CONFLICT, )
def test_export_import_db(data_dir, username): """Create a DB, write some data and then after export/import confirm it's there""" msg_aggregator = MessagesAggregator() data = DataHandler(data_dir, msg_aggregator) data.unlock(username, '123', create_new=True) starting_balance = ManuallyTrackedBalance( asset=A_EUR, label='foo', amount=FVal(10), location=Location.BANKS, tags=None, ) data.db.add_manually_tracked_balances([starting_balance]) encoded_data, _ = data.compress_and_encrypt_db('123') # The server would return them decoded encoded_data = encoded_data.decode() # pylint: disable=no-member data.decompress_and_decrypt_db('123', encoded_data) balances = data.db.get_manually_tracked_balances() assert balances == [starting_balance]
def test_query_all_balances_with_manually_tracked_balances( rotkehlchen_api_server_with_exchanges, ethereum_accounts, btc_accounts, manually_tracked_balances, ): """Test that using the query all balances endpoint also includes manually tracked balances""" # Disable caching of query results rotki = rotkehlchen_api_server_with_exchanges.rest_api.rotkehlchen rotki.chain_manager.cache_ttl_secs = 0 manually_tracked_balances = [ ManuallyTrackedBalance( asset=A_BTC, label='XPUB BTC wallet', amount=FVal('10'), location=Location.BLOCKCHAIN, tags=None, ), ManuallyTrackedBalance( asset=A_BTC, label='BTC in hardware wallet', amount=FVal('20'), location=Location.BLOCKCHAIN, tags=['private'], ), ManuallyTrackedBalance( asset=A_ETH, label='ETH in a not supported exchange wallet', amount=FVal('10'), location=Location.EXTERNAL, tags=['private'], ), ManuallyTrackedBalance( asset=A_EUR, label='N26 account', amount=FVal('12500.15'), location=Location.BANKS, tags=None, ), ManuallyTrackedBalance( asset=A_EUR, label='Deutsche Bank account', amount=FVal('1337.1337'), location=Location.BANKS, tags=None, ) ] setup = setup_balances( rotki=rotki, ethereum_accounts=ethereum_accounts, btc_accounts=btc_accounts, manually_tracked_balances=manually_tracked_balances, ) # now do the same but save the data in the DB and test it works # `save_data` is False by default but data will save since this is a fresh account with ExitStack() as stack: setup.enter_all_patches(stack) response = requests.get( api_url_for( rotkehlchen_api_server_with_exchanges, "allbalancesresource", ), ) result = assert_proper_response_with_result(response) assert_all_balances( result=result, db=rotki.data.db, expected_data_in_db=True, setup=setup, )
def test_query_all_balances( rotkehlchen_api_server_with_exchanges, ethereum_accounts, btc_accounts, ): """Test that using the query all balances endpoint works Test that balances from various sources are returned. Such as exchanges, blockchain and manually tracked balances""" async_query = random.choice([False, True]) # Disable caching of query results rotki = rotkehlchen_api_server_with_exchanges.rest_api.rotkehlchen rotki.chain_manager.cache_ttl_secs = 0 setup = setup_balances( rotki=rotki, ethereum_accounts=ethereum_accounts, btc_accounts=btc_accounts, manually_tracked_balances=[ ManuallyTrackedBalance( asset=A_EUR, label='My EUR bank', amount=FVal('1550'), location=Location.BANKS, tags=None, ) ], ) # Test that all balances request saves data on a fresh account with ExitStack() as stack: setup.enter_all_patches(stack) response = requests.get( api_url_for( rotkehlchen_api_server_with_exchanges, "allbalancesresource", ), json={'async_query': async_query}, ) if async_query: task_id = assert_ok_async_response(response) outcome = wait_for_async_task_with_result( rotkehlchen_api_server_with_exchanges, task_id, ) else: outcome = assert_proper_response_with_result(response) errors = rotki.msg_aggregator.consume_errors() assert len(errors) == 0 assert_all_balances( result=outcome, db=rotki.data.db, expected_data_in_db=True, setup=setup, ) last_save_timestamp = rotki.data.db.get_last_balance_save_time() # now do the same but check to see if the balance save frequency delay works # and thus data will not be saved with ExitStack() as stack: setup.enter_all_patches(stack) response = requests.get( api_url_for( rotkehlchen_api_server_with_exchanges, "allbalancesresource", ), ) assert_proper_response(response) new_save_timestamp = rotki.data.db.get_last_balance_save_time() assert last_save_timestamp == new_save_timestamp # wait for at least 1 second to make sure that new balances can be saved. # Can't save balances again if it's the same timestamp gevent.sleep(1) # now do the same but test that balance are saved since the balance save frequency delay # is overriden via `save_data` = True with ExitStack() as stack: setup.enter_all_patches(stack) response = requests.get( api_url_for( rotkehlchen_api_server_with_exchanges, "allbalancesresource", ), json={'save_data': True}, ) assert_proper_response(response) new_save_timestamp = rotki.data.db.get_last_balance_save_time() assert last_save_timestamp != new_save_timestamp
def make_manually_tracked_balances( # pylint: disable=no-self-use self, data: Dict[str, Any], **_kwargs: Any, ) -> ManuallyTrackedBalance: return ManuallyTrackedBalance(**data)
) msg = 'call count should increase since cache should have been ignored' assert all(fn.call_count == 2 for fn in function_call_counters), msg @pytest.mark.parametrize('tags', [[{ 'name': 'private', 'description': 'My private accounts', 'background_color': 'ffffff', 'foreground_color': '000000', }]]) @pytest.mark.parametrize('manually_tracked_balances', [[ ManuallyTrackedBalance( asset=A_BTC, label='XPUB BTC wallet', amount=FVal('10'), location=Location.BLOCKCHAIN, tags=None, ), ManuallyTrackedBalance( asset=A_BTC, label='BTC in hardware wallet', amount=FVal('20'), location=Location.BLOCKCHAIN, tags=['private'], ), ManuallyTrackedBalance( asset=A_ETH, label='ETH in a not supported exchange wallet', amount=FVal('10'), location=Location.EXTERNAL,
def test_query_statistics_value_distribution( rotkehlchen_api_server_with_exchanges, ethereum_accounts, btc_accounts, start_with_valid_premium, ): """Test that using the statistics value distribution endpoint works""" start_time = ts_now() # Disable caching of query results rotki = rotkehlchen_api_server_with_exchanges.rest_api.rotkehlchen rotki.chain_manager.cache_ttl_secs = 0 token_balances = {A_RDN: ['111000', '4000000']} setup = setup_balances( rotki=rotki, ethereum_accounts=ethereum_accounts, btc_accounts=btc_accounts, token_balances=token_balances, manually_tracked_balances=[ ManuallyTrackedBalance( asset=A_EUR, label='My EUR bank', amount=FVal('1550'), location=Location.BANKS, tags=None, balance_type=BalanceType.ASSET, ) ], ) # query balances and save data in DB to have data to test the statistics endpoint with ExitStack() as stack: setup.enter_all_patches(stack) response = requests.get( api_url_for( rotkehlchen_api_server_with_exchanges, "allbalancesresource", ), json={'save_data': True}, ) assert_proper_response(response) def assert_okay_by_location(response): """Helper function to run next query and its assertion twice""" if start_with_valid_premium: result = assert_proper_response_with_result(response) assert len(result) == 5 locations = {'poloniex', 'binance', 'banks', 'blockchain', 'total'} for entry in result: assert len(entry) == 3 assert entry['time'] >= start_time assert entry['usd_value'] is not None assert entry['location'] in locations locations.remove(entry['location']) assert len(locations) == 0 else: assert_error_response( response=response, contained_in_msg= 'logged in user testuser does not have a premium subscription', status_code=HTTPStatus.CONFLICT, ) # and now test that statistics work fine for distribution by location for json body response = requests.get( api_url_for( rotkehlchen_api_server_with_exchanges, "statisticsvaluedistributionresource", ), json={'distribution_by': 'location'}, ) assert_okay_by_location(response) # and now test that statistics work fine for distribution by location for query params response = requests.get( api_url_for( rotkehlchen_api_server_with_exchanges, "statisticsvaluedistributionresource", ) + '?distribution_by=location', ) assert_okay_by_location(response) # finally test that statistics work fine for distribution by asset response = requests.get( api_url_for( rotkehlchen_api_server_with_exchanges, "statisticsvaluedistributionresource", ), json={'distribution_by': 'asset'}, ) if start_with_valid_premium: result = assert_proper_response_with_result(response) assert len(result) == 4 totals = { 'ETH': get_asset_balance_total(A_ETH, setup), 'BTC': get_asset_balance_total(A_BTC, setup), 'EUR': get_asset_balance_total(A_EUR, setup), A_RDN.identifier: get_asset_balance_total(A_RDN, setup), } for entry in result: assert len(entry) == 5 assert entry['time'] >= start_time assert entry['category'] == 'asset' assert entry['usd_value'] is not None assert FVal(entry['amount']) == totals[entry['asset']] else: assert_error_response( response=response, contained_in_msg= 'logged in user testuser does not have a premium subscription', status_code=HTTPStatus.CONFLICT, )