def test_query_historical_dsr_with_a_zero_withdrawal( rotkehlchen_api_server, ethereum_accounts, inquirer, # pylint: disable=unused-argument ): """Test DSR for an account that was opened while DSR is 0 and made a 0 DAI withdrawal Essentially reproduce DSR problem reported here: https://github.com/rotki/rotki/issues/1032 The account in question operates in a zero DSR environment but the reported problem seems to be just because he tried a zero DAI withdrawal """ rotki = rotkehlchen_api_server.rest_api.rotkehlchen original_get_logs = rotki.chain_manager.ethereum.get_logs proxies_mapping = { # proxy for 0x714696C5a872611F76655Bc163D0131cBAc60a70 ethereum_accounts[0]: '0xAe9996b76bdAa003ace6D66328A6942565f5768d', } mock_proxies(rotki, proxies_mapping, 'makerdao_dsr') # Query only until a block we know DSR is 0 and we know the number # of DSR events def mock_get_logs( contract_address, abi, event_name, argument_filters, from_block, to_block='latest', # pylint: disable=unused-argument ): return original_get_logs( contract_address, abi, event_name, argument_filters, from_block, to_block=10149816, # A block at which DSR is still zero ) patched_get_logs = patch.object( rotki.chain_manager.ethereum, 'get_logs', side_effect=mock_get_logs, ) with patched_get_logs: response = requests.get( api_url_for( rotkehlchen_api_server, "makerdaodsrhistoryresource", )) assert_proper_response(response) json_data = response.json() assert json_data['message'] == '' result = json_data['result'][ethereum_accounts[0]] assert FVal(result['gain_so_far']) == ZERO assert FVal(result['gain_so_far_usd_value']) == ZERO movements = result['movements'] expected_movements = [{ 'movement_type': 'deposit', 'gain_so_far': ZERO, 'gain_so_far_usd_value': ZERO, 'amount': FVal('79'), 'amount_usd_value': FVal('79'), 'block_number': 9953028, 'timestamp': 1587970286, 'tx_hash': '0x988aea85b54c5b2834b144e9f7628b524bf9faf3b87821aa520b7bcfb57ab289', }, { 'movement_type': 'withdrawal', 'gain_so_far': ZERO, 'gain_so_far_usd_value': ZERO, 'amount': FVal('79'), 'amount_usd_value': FVal('79'), 'block_number': 9968906, 'timestamp': 1588182567, 'tx_hash': '0x2a1bee69b9bafe031026dbcc8f199881b568fd767482b5436dd1cd94f2642443', }, { 'movement_type': 'withdrawal', 'gain_so_far': ZERO, 'gain_so_far_usd_value': ZERO, 'amount': ZERO, 'amount_usd_value': ZERO, 'block_number': 9968906, 'timestamp': 1588182567, 'tx_hash': '0x618fc9542890a2f58ab20a3c12d173b3638af11fda813e61788e242b4fc9a756', }] assert_serialized_lists_equal(movements, expected_movements, max_diff="1e-26") errors = rotki.msg_aggregator.consume_errors() assert len(errors) == 0
def test_user_creation_with_invalid_premium_credentials( rotkehlchen_api_server, data_dir): """ Test that invalid and unauthenticated premium credentials are handled at new user creation """ # Create a user with invalid credentials username = '******' data = { 'name': username, 'password': '******', 'premium_api_key': 'foo', 'premium_api_secret': 'boo', } response = requests.put(api_url_for(rotkehlchen_api_server, "usersresource"), json=data) assert_error_response( response=response, contained_in_msg='Provided API/Key secret format is invalid', ) # Check that the directory was NOT created assert not Path( data_dir / username).exists(), 'The directory should not have been created' # Create a new user with valid but not authenticable credentials username = '******' data = { 'name': username, 'password': '******', 'premium_api_key': VALID_PREMIUM_KEY, 'premium_api_secret': VALID_PREMIUM_SECRET, } response = requests.put(api_url_for(rotkehlchen_api_server, "usersresource"), json=data) expected_msg = ( 'Could not verify keys for the new account. Rotkehlchen API key was rejected by server' ) assert_error_response( response=response, contained_in_msg=expected_msg, status_code=HTTPStatus.CONFLICT, ) # Check that the directory was NOT created assert not Path( data_dir / username).exists(), 'The directory should not have been created' # But check that a backup of the directory was made just in case backups = list(Path(data_dir).glob('auto_backup_*')) assert len(backups) == 1 assert 'auto_backup_Anja_' in str( backups[0]), 'An automatic backup should have been made' # But then try to create a normal-non premium user and see it works username = '******' data = { 'name': username, 'password': '******', } response = requests.put(api_url_for(rotkehlchen_api_server, "usersresource"), json=data) assert_proper_response(response) check_proper_unlock_result(response.json()) # Query users and make sure the new user is logged in response = requests.get( api_url_for(rotkehlchen_api_server, "usersresource")) assert_proper_response(response) json = response.json() assert json['result'][username] == 'loggedin' assert len(json['result']) == 2 # Check that the directory was created assert Path(data_dir / username / 'rotkehlchen.db').exists()
def test_user_password_change(rotkehlchen_api_server, username, db_password): """ Test that changing a logged-in user's users password works successfully and that common errors are handled. """ # wrong username data_wrong_user = { 'name': 'billybob', 'current_password': '******', 'new_password': '******', } response = requests.patch(api_url_for(rotkehlchen_api_server, "userpasswordchangeresource", name=username), json=data_wrong_user) msg = ( f'Provided user "{data_wrong_user["name"]}" is not the logged in user') assert_error_response( response=response, contained_in_msg=msg, status_code=HTTPStatus.BAD_REQUEST, ) # wrong password data_wrong_pass = { 'name': username, 'current_password': '******', 'new_password': '******', } response = requests.patch(api_url_for(rotkehlchen_api_server, "userpasswordchangeresource", name=username), json=data_wrong_pass) msg = ('Provided current password is not correct') assert_error_response( response=response, contained_in_msg=msg, status_code=HTTPStatus.UNAUTHORIZED, ) # success data_success = { 'name': username, 'current_password': db_password, 'new_password': '******', } response = requests.patch(api_url_for(rotkehlchen_api_server, "userpasswordchangeresource", name=username), json=data_success) assert_simple_ok_response(response) # revert password data_revert = { 'name': username, 'current_password': data_success['new_password'], 'new_password': db_password, } response = requests.patch(api_url_for(rotkehlchen_api_server, "userpasswordchangeresource", name=username), json=data_revert) assert_simple_ok_response(response)
def test_query_vaults_usdc(rotkehlchen_api_server, ethereum_accounts): """Check vault info and details for a vault with USDC as collateral""" rotki = rotkehlchen_api_server.rest_api.rotkehlchen proxies_mapping = { ethereum_accounts[0]: '0xBE79958661741079679aFf75DbEd713cE71a979d', # 7588 } mock_proxies(rotki, proxies_mapping) response = requests.get( api_url_for( rotkehlchen_api_server, "makerdaovaultsresource", )) vaults = assert_proper_response_with_result(response) vault_7588 = MakerDAOVault( identifier=7588, owner=ethereum_accounts[0], collateral_type='USDC-A', urn='0x56D88244073B2fC17af5B1E6088936D5bAaDc37B', collateral_asset=A_USDC, collateral_amount=ZERO, collateral_usd_value=ZERO, debt_value=ZERO, collateralization_ratio=None, liquidation_ratio=FVal(1.2), liquidation_price=None, stability_fee=FVal(0.0075), ) expected_vaults = [vault_7588.serialize()] assert_serialized_lists_equal(expected_vaults, vaults) response = requests.get( api_url_for( rotkehlchen_api_server, "makerdaovaultdetailsresource", )) vault_7588_details = { 'identifier': 7588, 'creation_ts': 1585286480, 'total_interest_owed': FVal('0.00050636718'), 'total_liquidated_amount': ZERO, 'total_liquidated_usd': ZERO, 'events': [{ 'event_type': 'deposit', 'amount': FVal('45'), 'amount_usd_value': FVal('45'), 'timestamp': 1585286480, 'tx_hash': '0x8b553dd0e8ee5385ec91105bf911143666d9df0ecd84c04f288278f7658aa7d6', }, { 'event_type': 'generate', 'amount': FVal('20'), 'amount_usd_value': FVal('20.46'), 'timestamp': 1585286480, 'tx_hash': '0x8b553dd0e8ee5385ec91105bf911143666d9df0ecd84c04f288278f7658aa7d6', }, { 'event_type': 'generate', 'amount': FVal('15.99'), 'amount_usd_value': FVal('16.35777'), 'timestamp': 1585286769, 'tx_hash': '0xdb861c893a51e4649ff3740cd3658cd4c9b1d048d3b8b4d117f4319bd60aee01', }, { 'event_type': 'payback', 'amount': FVal('35.990506367'), 'amount_usd_value': FVal('36.818288'), 'timestamp': 1585290263, 'tx_hash': '0xdd7825fe4a93c6f1ffa25a91b6da2396c229fe16b17242ad5c0bf7962928b2ec', }, { 'event_type': 'withdraw', 'amount': FVal('45'), 'amount_usd_value': FVal('45'), 'timestamp': 1585290300, 'tx_hash': '0x97462ebba7ce2467787bf6de25a25c24e538cf8a647919112c5f048b6a293408', }], } details = assert_proper_response_with_result(response) expected_details = [vault_7588_details] assert_serialized_lists_equal(expected_details, details)
def test_query_vaults_usdc_strange(rotkehlchen_api_server, ethereum_accounts): """Strange case of a USDC vault that is not queried correctly https://oasis.app/borrow/7538?network=mainnet """ rotki = rotkehlchen_api_server.rest_api.rotkehlchen proxies_mapping = { ethereum_accounts[0]: '0x15fEaFd4358b8C03c889D6661b0CA1Be3389792F', # 7538 } mock_proxies(rotki, proxies_mapping) response = requests.get( api_url_for( rotkehlchen_api_server, "makerdaovaultsresource", )) # That proxy has 3 vaults. We only want to test 7538, which is closed/repaid so just keep that vaults = [ x for x in assert_proper_response_with_result(response) if x['identifier'] == 7538 ] vault_7538 = MakerDAOVault( identifier=7538, owner=ethereum_accounts[0], collateral_type='USDC-A', urn='0x70E58566C7baB6faaFE03fbA69DF45Ef4f48223B', collateral_asset=A_USDC, collateral_amount=ZERO, collateral_usd_value=ZERO, debt_value=ZERO, collateralization_ratio=None, liquidation_ratio=FVal(1.2), liquidation_price=None, ) expected_vaults = [vault_7538.serialize()] assert_serialized_lists_equal(expected_vaults, vaults) # And also make sure that the internal mapping will only query details of 7538 rotki.chain_manager.makerdao.vault_mappings = { ethereum_accounts[0]: [vault_7538] } response = requests.get( api_url_for( rotkehlchen_api_server, "makerdaovaultdetailsresource", )) vault_7538_details = { 'identifier': 7538, 'creation_ts': 1585145754, 'total_interest_owed': FVal('0.0005943266'), 'total_liquidated_amount': ZERO, 'total_liquidated_usd': ZERO, 'events': [{ 'event_type': 'deposit', 'amount': FVal('250.12'), 'timestamp': 1588664698, 'tx_hash': '0x9ba4a6187fa2c49ba327e7c923846a08a1e972017ec41d3f9f66ef524f7dde59', }, { 'event_type': 'generate', 'amount': FVal('25'), 'timestamp': 1588664698, 'tx_hash': '0x9ba4a6187fa2c49ba327e7c923846a08a1e972017ec41d3f9f66ef524f7dde59', }, { 'event_type': 'payback', 'amount': FVal('25.000248996'), 'timestamp': 1588696496, 'tx_hash': '0x8bd960e7eb8b9e2b81d2446d1844dd63f94636c7800ea5e3b4d926ea0244c66c', }, { 'event_type': 'deposit', 'amount': FVal('0.0113'), 'timestamp': 1588720248, 'tx_hash': '0x678c4da562173c102473f1904ff293a767ebac9ec6c7d728ef2fd41acf00a13a', }], } details = assert_proper_response_with_result(response) expected_details = [vault_7538_details] assert_serialized_lists_equal(expected_details, details)
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, )
def test_query_vaults_details_liquidation(rotkehlchen_api_server, ethereum_accounts): """Check vault details of a vault with liquidations Also use three accounts, two of which have vaults associated with them to test that vaults for multiple accounts get detected """ rotki = rotkehlchen_api_server.rest_api.rotkehlchen proxies_mapping = { ethereum_accounts[0]: '0x689D4C2229717f877A644A0aAd742D67E5D0a2FB', ethereum_accounts[2]: '0x420F88De6dadA0a77Db7b9EdBe3A0C614346031E', } mock_proxies(rotki, proxies_mapping) response = requests.get( api_url_for( rotkehlchen_api_server, "makerdaovaultsresource", )) vaults = assert_proper_response_with_result(response) vault_6021 = { 'identifier': 6021, 'owner': ethereum_accounts[2], 'collateral_type': 'ETH-A', 'collateral_asset': 'ETH', 'collateral_amount': ZERO, 'collateral_usd_value': ZERO, 'debt_value': ZERO, 'collateralization_ratio': None, 'liquidation_ratio': '150.00%', 'liquidation_price': None, 'stability_fee': '0.00%', } vault_8015_with_owner = VAULT_8015.copy() vault_8015_with_owner['owner'] = ethereum_accounts[0] assert_serialized_dicts_equal(vault_6021, vaults[0]) assert_serialized_dicts_equal( vault_8015_with_owner, vaults[1], ignore_keys=VAULT_IGNORE_KEYS, ) assert len(vaults) == 2 response = requests.get( api_url_for( rotkehlchen_api_server, "makerdaovaultdetailsresource", )) vault_6021_details = { 'identifier': 6021, 'creation_ts': 1582699808, 'total_interest_owed': FVal('-11078.655097848869'), 'total_liquidated_amount': FVal('141.7'), 'total_liquidated_usd': FVal('19191.848'), 'events': [{ 'event_type': 'deposit', 'amount': FVal('140'), 'amount_usd_value': FVal('31322.2'), 'timestamp': 1582699808, 'tx_hash': '0x3246ef91fd3d6e1f7c5766de4fa1f0991ba67d92e518447ba8207fe98569c309', }, { 'event_type': 'generate', 'amount': FVal('14000'), 'amount_usd_value': FVal('14028'), 'timestamp': 1582699808, 'tx_hash': '0x3246ef91fd3d6e1f7c5766de4fa1f0991ba67d92e518447ba8207fe98569c309', }, { 'event_type': 'deposit', 'amount': FVal('1.7'), 'amount_usd_value': FVal('331.262'), 'timestamp': 1583958747, 'tx_hash': '0x65ac798cb9f22068e43fd9ef8303a31e436989062ae87e25650cc44c7788ab62', }, { 'event_type': 'payback', 'amount': FVal('2921.344902'), 'amount_usd_value': FVal('2927.187591'), 'timestamp': 1584024065, 'tx_hash': '0x6e44d22d6898ee012369787cd75ea6fb9ace6f995cd157675f370e8ba4a7b9ad', }, { 'event_type': 'liquidation', 'amount': FVal('50'), 'amount_usd_value': FVal('6772'), 'timestamp': 1584061534, 'tx_hash': '0xb02050d914ab40f59a9e07eb4f8161ce36eb97cea9c189b027eb1ceeac83a516', }, { 'event_type': 'liquidation', 'amount': FVal('50'), 'amount_usd_value': FVal('6772'), 'timestamp': 1584061897, 'tx_hash': '0x678f31d49dd70d76c0ce441343c0060dc600f4c8dbb4cee2b08c6b451b6097cd', }, { 'event_type': 'liquidation', 'amount': FVal('41.7'), 'amount_usd_value': FVal('5647.848'), 'timestamp': 1584061977, 'tx_hash': '0xded0f9de641087692555d92a7fa94fa9fa7abf22744b2d16c20a66c5e48a8edf', }], } details = assert_proper_response_with_result(response) assert len(details) == 2 assert_serialized_dicts_equal(vault_6021_details, details[0]) assert_serialized_dicts_equal(VAULT_8015_DETAILS, details[1], length_list_keymap={'events': 7})
def test_query_asset_movements(rotkehlchen_api_server_with_exchanges): """Test that using the asset movements query endpoint works fine""" async_query = random.choice([False, True]) server = rotkehlchen_api_server_with_exchanges setup = prepare_rotki_for_history_processing_test( server.rest_api.rotkehlchen) # setup = mock_history_processing_and_exchanges(server.rest_api.rotkehlchen) # query asset movements of one specific exchange with setup.polo_patch: response = requests.get( api_url_for( server, 'assetmovementsresource', ), json={ 'location': 'poloniex', 'async_query': async_query }, ) if async_query: task_id = assert_ok_async_response(response) outcome = wait_for_async_task( rotkehlchen_api_server_with_exchanges, task_id) result = outcome['result'] else: result = assert_proper_response_with_result(response) assert result['entries_found'] == 4 assert result['entries_limit'] == FREE_ASSET_MOVEMENTS_LIMIT poloniex_ids = [x['entry']['identifier'] for x in result['entries']] assert_poloniex_asset_movements([x['entry'] for x in result['entries']], deserialized=True) assert all( x['ignored_in_accounting'] is False for x in result['entries']), 'ignored should be false' # noqa: E501 # now let's ignore all poloniex action ids response = requests.put( api_url_for( rotkehlchen_api_server_with_exchanges, 'ignoredactionsresource', ), json={ 'action_type': 'asset movement', 'action_ids': poloniex_ids }, ) result = assert_proper_response_with_result(response) assert set(result['asset movement']) == set(poloniex_ids) # query asset movements of all exchanges with setup.polo_patch: response = requests.get( api_url_for(server, 'assetmovementsresource'), json={'async_query': async_query}, ) if async_query: task_id = assert_ok_async_response(response) outcome = wait_for_async_task( rotkehlchen_api_server_with_exchanges, task_id) result = outcome['result'] else: result = assert_proper_response_with_result(response) movements = result['entries'] assert_poloniex_asset_movements([ x['entry'] for x in movements if x['entry']['location'] == 'poloniex' ], True) # noqa: E501 assert_kraken_asset_movements( [x['entry'] for x in movements if x['entry']['location'] == 'kraken'], True) # noqa: E501 def assert_okay(response): """Helper function for DRY checking below assertions""" if async_query: task_id = assert_ok_async_response(response) outcome = wait_for_async_task( rotkehlchen_api_server_with_exchanges, task_id) result = outcome['result'] else: result = assert_proper_response_with_result(response) movements = result['entries'] assert_poloniex_asset_movements( to_check_list=[ x['entry'] for x in movements if x['entry']['location'] == 'poloniex' ], deserialized=True, movements_to_check=(1, 2), ) msg = 'poloniex asset movements should have now been ignored for accounting' assert all(x['ignored_in_accounting'] is True for x in movements if x['entry']['location'] == 'poloniex'), msg # noqa: E501 assert_kraken_asset_movements( to_check_list=[ x['entry'] for x in movements if x['entry']['location'] == 'kraken' ], deserialized=True, movements_to_check=(0, 1, 2), ) # and now query them in a specific time range excluding some asset movements data = { 'from_timestamp': 1439994442, 'to_timestamp': 1458994442, 'async_query': async_query } with setup.polo_patch: response = requests.get(api_url_for(server, "assetmovementsresource"), json=data) assert_okay(response) # do the same but with query args. This serves as test of from/to timestamp with query args with setup.polo_patch: response = requests.get( api_url_for(server, "assetmovementsresource") + '?' + urlencode(data)) assert_okay(response)
def test_setup_exchange(rotkehlchen_api_server): """Test that setting up an exchange via the api works""" # Check that no exchanges are registered response = requests.get( api_url_for(rotkehlchen_api_server, "exchangesresource")) assert_proper_response(response) json_data = response.json() assert json_data['message'] == '' assert json_data['result'] == [] # First test that if api key validation fails we get an error, for every exchange for location in SUPPORTED_EXCHANGES: data = { 'location': str(location), 'name': f'my_{str(location)}', 'api_key': 'ddddd', 'api_secret': 'fffffff' } # noqa: E501 if location in (Location.COINBASEPRO, Location.KUCOIN): data['passphrase'] = '123' response = requests.put( api_url_for(rotkehlchen_api_server, 'exchangesresource'), json=data, ) assert_error_response( response=response, contained_in_msg=[ 'Provided API Key or secret is invalid', 'Provided API Key is invalid', 'Provided API Key is in invalid Format', 'Provided API Secret is invalid', 'Provided Gemini API key needs to have "Auditor" permission activated', BITSTAMP_API_KEY_ERROR_CODE_ACTION['API0011'], BITFINEX_API_KEY_ERROR_MESSAGE, KUCOIN_API_KEY_ERROR_CODE[400003], 'Bad combination of API Keys', ], status_code=HTTPStatus.CONFLICT, ) # Make sure that no exchange is registered after that rotki = rotkehlchen_api_server.rest_api.rotkehlchen assert len(rotki.exchange_manager.connected_exchanges) == 0 # Mock the api pair validation and make sure that the exchange is setup data = { 'location': 'kraken', 'name': 'my_kraken', 'api_key': 'ddddd', 'api_secret': 'fffffff' } # noqa: E501 with mock_validate_api_key_success(Location.KRAKEN): response = requests.put( api_url_for(rotkehlchen_api_server, 'exchangesresource'), json=data, ) assert_simple_ok_response(response) # and check that kraken is now registered response = requests.get( api_url_for(rotkehlchen_api_server, 'exchangesresource')) result = assert_proper_response_with_result(response) assert result == [{ 'location': 'kraken', 'name': 'my_kraken', 'kraken_account_type': 'starter' }] # noqa: E501 # Check that we get an error if we try to re-setup an already setup exchange data = { 'location': 'kraken', 'name': 'my_kraken', 'api_key': 'ddddd', 'api_secret': 'fffffff' } # noqa: E501 with mock_validate_api_key_success(Location.KRAKEN): response = requests.put( api_url_for(rotkehlchen_api_server, 'exchangesresource'), json=data, ) assert_error_response( response=response, contained_in_msg='kraken exchange my_kraken is already registered', status_code=HTTPStatus.CONFLICT, ) # But check that same location different name works data = { 'location': 'kraken', 'name': 'my_other_kraken', 'api_key': 'aadddddd', 'api_secret': 'fffffff' } # noqa: E501 with mock_validate_api_key_success(Location.KRAKEN): response = requests.put( api_url_for(rotkehlchen_api_server, 'exchangesresource'), json=data, ) assert_simple_ok_response(response) # and check that kraken is now registered response = requests.get( api_url_for(rotkehlchen_api_server, 'exchangesresource')) result = assert_proper_response_with_result(response) assert result == [ { 'location': 'kraken', 'name': 'my_kraken', 'kraken_account_type': 'starter' }, { 'location': 'kraken', 'name': 'my_other_kraken', 'kraken_account_type': 'starter' }, ] # Check that giving a passphrase is fine data = { 'location': 'coinbasepro', 'name': 'my_coinbasepro', 'api_key': 'ddddd', 'api_secret': 'fffff', 'passphrase': 'sdf' } # noqa: E501 with mock_validate_api_key_success(Location.COINBASEPRO): response = requests.put( api_url_for(rotkehlchen_api_server, 'exchangesresource'), json=data, ) assert_simple_ok_response(response) # and check that coinbasepro is now registered response = requests.get( api_url_for(rotkehlchen_api_server, 'exchangesresource')) result = assert_proper_response_with_result(response) assert result == [ { 'location': 'kraken', 'name': 'my_kraken', 'kraken_account_type': 'starter' }, { 'location': 'kraken', 'name': 'my_other_kraken', 'kraken_account_type': 'starter' }, { 'location': 'coinbasepro', 'name': 'my_coinbasepro' }, ]
def test_remove_exchange(rotkehlchen_api_server): """Test that removing a setup exchange via the api works""" rotki = rotkehlchen_api_server.rest_api.rotkehlchen db = rotki.data.db # Setup coinbase exchange data = { 'location': 'coinbase', 'name': 'foo', 'api_key': 'ddddd', 'api_secret': 'fffffff' } with mock_validate_api_key_success(Location.COINBASE): response = requests.put( api_url_for(rotkehlchen_api_server, 'exchangesresource'), json=data, ) assert_simple_ok_response(response) # and check it's registered response = requests.get( api_url_for(rotkehlchen_api_server, 'exchangesresource')) result = assert_proper_response_with_result(response) assert result == [{'location': 'coinbase', 'name': 'foo'}] # Add query ranges to see that they also get deleted when removing the exchange cursor = db.conn.cursor() cursor.executemany( 'INSERT OR REPLACE INTO used_query_ranges(name, start_ts, end_ts) VALUES (?, ?, ?)', [('coinbasepro_trades', 0, 1579564096), ('coinbasepro_margins', 0, 1579564096), ('coinbasepro_asset_movements', 0, 1579564096), ('coinbase_trades', 0, 1579564096), ('coinbase_margins', 0, 1579564096), ('coinbase_asset_movements', 0, 1579564096), ('binance_trades', 0, 1579564096), ('binance_margins', 0, 1579564096), ('binance_asset_movements', 0, 1579564096)], ) # Now remove the registered coinbase exchange data = {'location': 'coinbase', 'name': 'foo'} response = requests.delete(api_url_for(rotkehlchen_api_server, 'exchangesresource'), json=data) assert_simple_ok_response(response) # and check that it's not registered anymore response = requests.get( api_url_for(rotkehlchen_api_server, 'exchangesresource')) result = assert_proper_response_with_result(response) assert result == [] # Also check that the coinbase query ranges have been deleted but not the other ones cursor = db.conn.cursor() result = cursor.execute('SELECT name from used_query_ranges') count = 0 for entry in result: count += 1 msg = 'only binance or coinbasepro query ranges should remain' assert 'binance' in entry[0] or 'coinbasepro' in entry[0], msg assert count == 6, 'only 6 query ranges should remain in the DB' # now try to remove a non-registered exchange data = {'location': 'binance', 'name': 'my_binance'} response = requests.delete(api_url_for(rotkehlchen_api_server, "exchangesresource"), json=data) assert_error_response( response=response, contained_in_msg='binance exchange my_binance is not registered', status_code=HTTPStatus.CONFLICT, )
def test_exchange_query_trades(rotkehlchen_api_server_with_exchanges): """Test that using the exchange trades query endpoint works fine""" async_query = random.choice([False, True]) server = rotkehlchen_api_server_with_exchanges setup = mock_history_processing_and_exchanges(server.rest_api.rotkehlchen) # query trades of one specific exchange with setup.binance_patch: response = requests.get( api_url_for( server, 'tradesresource', ), json={ 'location': 'binance', 'async_query': async_query }, ) if async_query: task_id = assert_ok_async_response(response) outcome = wait_for_async_task( rotkehlchen_api_server_with_exchanges, task_id) result = outcome['result'] else: result = assert_proper_response_with_result(response) assert result['entries_found'] > 0 assert result['entries_limit'] == FREE_TRADES_LIMIT assert_binance_trades_result([x['entry'] for x in result['entries']]) # query trades of all exchanges with setup.binance_patch, setup.polo_patch: response = requests.get( api_url_for(server, 'tradesresource'), json={'async_query': async_query}, ) if async_query: task_id = assert_ok_async_response(response) outcome = wait_for_async_task( rotkehlchen_api_server_with_exchanges, task_id) result = outcome['result'] else: result = assert_proper_response_with_result(response) trades = result['entries'] assert_binance_trades_result([ x['entry'] for x in trades if x['entry']['location'] == 'binance' ]) # noqa: E501 assert_poloniex_trades_result([ x['entry'] for x in trades if x['entry']['location'] == 'poloniex' ]) # noqa: E501 def assert_okay(response): """Helper function for DRY checking below assertions""" if async_query: task_id = assert_ok_async_response(response) outcome = wait_for_async_task( rotkehlchen_api_server_with_exchanges, task_id) result = outcome['result'] else: result = assert_proper_response_with_result(response) trades = result['entries'] assert_binance_trades_result([ x['entry'] for x in trades if x['entry']['location'] == 'binance' ]) # noqa: E501 assert_poloniex_trades_result( trades=[ x['entry'] for x in trades if x['entry']['location'] == 'poloniex' ], trades_to_check=(2, ), ) # and now query them in a specific time range excluding two of poloniex's trades data = { 'from_timestamp': 1499865548, 'to_timestamp': 1539713118, 'async_query': async_query } with setup.binance_patch, setup.polo_patch: response = requests.get(api_url_for(server, "tradesresource"), json=data) assert_okay(response) # do the same but with query args. This serves as test of from/to timestamp with query args with setup.binance_patch, setup.polo_patch: response = requests.get( api_url_for(server, "tradesresource") + '?' + urlencode(data)) assert_okay(response)
def test_setup_exchange_errors(rotkehlchen_api_server): """Test errors and edge cases of setup_exchange endpoint""" # Provide unsupported exchange location data = { 'location': 'notexisting', 'name': 'foo', 'api_key': 'ddddd', 'api_secret': 'fffffff' } with mock_validate_api_key_success(Location.KRAKEN): response = requests.put( api_url_for(rotkehlchen_api_server, 'exchangesresource'), json=data, ) assert_error_response( response=response, contained_in_msg='Failed to deserialize Location value notexisting', status_code=HTTPStatus.BAD_REQUEST, ) # Provide invalid type exchange location data = { 'location': 3434, 'name': 'foo', 'api_key': 'ddddd', 'api_secret': 'fffffff' } with mock_validate_api_key_success(Location.KRAKEN): response = requests.put( api_url_for(rotkehlchen_api_server, "exchangesresource"), json=data, ) assert_error_response( response=response, contained_in_msg= 'Failed to deserialize Location value from non string value: 3434', status_code=HTTPStatus.BAD_REQUEST, ) # Provide invalid type exchange name data = { 'location': 'kraken', 'name': 55, 'api_key': 'ddddd', 'api_secret': 'fffffff' } with mock_validate_api_key_success(Location.KRAKEN): response = requests.put( api_url_for(rotkehlchen_api_server, "exchangesresource"), json=data, ) assert_error_response( response=response, contained_in_msg='Not a valid string', status_code=HTTPStatus.BAD_REQUEST, ) # Omit exchange name and location data = {'api_key': 'ddddd', 'api_secret': 'fffffff'} with mock_validate_api_key_success(Location.KRAKEN): response = requests.put( api_url_for(rotkehlchen_api_server, "exchangesresource"), json=data, ) assert_error_response( response=response, contained_in_msg='Missing data for required field', status_code=HTTPStatus.BAD_REQUEST, ) # Provide invalid type for api key data = {'name': 'kraken', 'api_key': True, 'api_secret': 'fffffff'} with mock_validate_api_key_success(Location.KRAKEN): response = requests.put( api_url_for(rotkehlchen_api_server, "exchangesresource"), json=data, ) assert_error_response( response=response, contained_in_msg='Given API Key should be a string', status_code=HTTPStatus.BAD_REQUEST, ) # Omit api key data = {'name': 'kraken', 'api_secret': 'fffffff'} with mock_validate_api_key_success(Location.KRAKEN): response = requests.put( api_url_for(rotkehlchen_api_server, "exchangesresource"), json=data, ) assert_error_response( response=response, contained_in_msg='Missing data for required field', status_code=HTTPStatus.BAD_REQUEST, ) # Provide invalid type for api secret data = {'name': 'kraken', 'api_key': 'ddddd', 'api_secret': 234.1} with mock_validate_api_key_success(Location.KRAKEN): response = requests.put( api_url_for(rotkehlchen_api_server, "exchangesresource"), json=data, ) assert_error_response( response=response, contained_in_msg='Given API Secret should be a string', status_code=HTTPStatus.BAD_REQUEST, ) # Omit api secret data = {'name': 'kraken', 'api_key': 'ddddd'} with mock_validate_api_key_success(Location.KRAKEN): response = requests.put( api_url_for(rotkehlchen_api_server, "exchangesresource"), json=data, ) assert_error_response( response=response, contained_in_msg='Missing data for required field', status_code=HTTPStatus.BAD_REQUEST, )
def test_edit_exchange_account(rotkehlchen_api_server_with_exchanges): server = rotkehlchen_api_server_with_exchanges rotki = rotkehlchen_api_server_with_exchanges.rest_api.rotkehlchen kraken = try_get_first_exchange(rotki.exchange_manager, Location.KRAKEN) poloniex = try_get_first_exchange(rotki.exchange_manager, Location.POLONIEX) assert kraken.name == 'mockkraken' assert kraken.account_type == DEFAULT_KRAKEN_ACCOUNT_TYPE assert poloniex.name == 'poloniex' data = { 'name': 'mockkraken', 'location': 'kraken', 'new_name': 'my_kraken' } response = requests.patch(api_url_for(server, 'exchangesresource'), json=data) result = assert_proper_response_with_result(response) assert result is True kraken = try_get_first_exchange(rotki.exchange_manager, Location.KRAKEN) assert kraken.name == 'my_kraken' assert kraken.account_type == DEFAULT_KRAKEN_ACCOUNT_TYPE data = { 'name': 'poloniex', 'location': 'poloniex', 'new_name': 'my_poloniex' } response = requests.patch(api_url_for(server, 'exchangesresource'), json=data) result = assert_proper_response_with_result(response) assert result is True poloniex = try_get_first_exchange(rotki.exchange_manager, Location.POLONIEX) assert poloniex.name == 'my_poloniex' # Make sure that existing location exchange but wrong name returns error data = { 'name': 'some_poloniex', 'location': 'poloniex', 'new_name': 'other_poloniex' } response = requests.patch(api_url_for(server, 'exchangesresource'), json=data) assert_error_response( response=response, status_code=HTTPStatus.CONFLICT, contained_in_msg= 'Could not find poloniex exchange some_poloniex for editing', ) # Make sure that real location but not registered returns error data = {'name': 'kucoin', 'location': 'kucoin', 'new_name': 'other_kucoin'} response = requests.patch(api_url_for(server, 'exchangesresource'), json=data) assert_error_response( response=response, status_code=HTTPStatus.CONFLICT, contained_in_msg='Could not find kucoin exchange kucoin for editing', ) # Make sure that not existing location returns error data = { 'name': 'kucoin', 'location': 'fakeexchange', 'new_name': 'other_kucoin' } response = requests.patch(api_url_for(server, 'exchangesresource'), json=data) assert_error_response( response=response, status_code=HTTPStatus.BAD_REQUEST, contained_in_msg='Failed to deserialize Location value fakeexchange', )
def test_query_compound_history(rotkehlchen_api_server, ethereum_accounts): # pylint: disable=unused-argument # noqa: E501 """Check querying the compound history endpoint works. Uses real data""" rotki = rotkehlchen_api_server.rest_api.rotkehlchen setup = setup_balances( rotki, ethereum_accounts=ethereum_accounts, eth_balances=['1000000', '2000000', '33000030003', '42323213'], token_balances={}, btc_accounts=None, original_queries=['zerion', 'logs', 'blocknobytime'], ) # Since this test can be a bit slow we don't run both async and sync in the same test run # Instead we randomly choose one. Eventually both cases will be covered. async_query = random.choice([True, False]) with ExitStack() as stack: # patch ethereum/etherscan to not autodetect tokens setup.enter_ethereum_patches(stack) response = requests.get(api_url_for( rotkehlchen_api_server, "compoundhistoryresource", ), json={'async_query': async_query}) if async_query: task_id = assert_ok_async_response(response) # Big timeout since this test can take a long time outcome = wait_for_async_task( rotkehlchen_api_server, task_id, timeout=ASYNC_TASK_WAIT_TIMEOUT * 10, ) assert outcome['message'] == '' result = outcome['result'] else: result = assert_proper_response_with_result(response) assert len(result) == 5 expected_events = process_result_list(EXPECTED_EVENTS) # Check only 22 first events, since this is how many there were in the time of # the writing of the test. Also don't check events for one of the addresses # as it's added later, has many events and it's only to see we handle repay correctly to_check_events = [ x for x in result['events'] if x['address'] != '0x65304d6aff5096472519ca86a6a1fea31cb47Ced' ] assert to_check_events[:22] == expected_events # Check one repay event other_events = [ x for x in result['events'] if x['address'] == '0x65304d6aff5096472519ca86a6a1fea31cb47Ced' ] assert other_events[12]['event_type'] == 'repay' expected_hash = '0x48a3e2ef8a746383deac34d74f2f0ea0451b2047701fbed4b9d769a782888eea' assert other_events[12]['tx_hash'] == expected_hash assert other_events[12]['value']['amount'] == '0.55064402' # Check interest profit mappings profit_0 = result['interest_profit'][ '0x2B888954421b424C5D3D9Ce9bB67c9bD47537d12'] assert FVal(profit_0[A_DAI.identifier]['amount']) > FVal(9) profit_1 = result['interest_profit'][ '0xC440f3C87DC4B6843CABc413916220D4f4FeD117'] assert FVal(profit_1[A_USDC.identifier]['amount']) > FVal(2) profit_2 = result['interest_profit'][ '0xF59D4937BF1305856C3a267bB07791507a3377Ee'] assert FVal(profit_2[A_DAI.identifier]['amount']) > FVal('0.3') # Check debt loss mappings debt_0 = result['debt_loss']['0xC440f3C87DC4B6843CABc413916220D4f4FeD117'] assert FVal(debt_0[A_CUSDC.identifier]['amount']) > FVal('84') assert FVal(debt_0['ETH']['amount']) > FVal('0.000012422') # Check liquidation profit mappings lprofit_0 = result['liquidation_profit'][ '0xC440f3C87DC4B6843CABc413916220D4f4FeD117'] assert FVal(lprofit_0['ETH']['amount']) > FVal('0.000012') # Check rewards mappings rewards_0 = result['rewards']['0xC440f3C87DC4B6843CABc413916220D4f4FeD117'] assert FVal(rewards_0[A_COMP.identifier]['amount']) > FVal('0.000036') rewards_1 = result['rewards']['0xF59D4937BF1305856C3a267bB07791507a3377Ee'] assert FVal(rewards_1[A_COMP.identifier]['amount']) > FVal('0.003613')
def test_add_and_query_ledger_actions(rotkehlchen_api_server, start_with_valid_premium): """Test that querying the ledger actions endpoint works as expected""" actions = _add_ledger_actions(rotkehlchen_api_server) response = requests.get( api_url_for( rotkehlchen_api_server, 'ledgeractionsresource', ), ) result = assert_proper_response_with_result(response) assert result['entries_found'] == 4 assert result['entries_total'] == 4 assert result[ 'entries_limit'] == -1 if start_with_valid_premium else FREE_LEDGER_ACTIONS_LIMIT assert all(x['ignored_in_accounting'] is False for x in result['entries'] ), 'by default nothing should be ignored' # noqa: E501 result = [x['entry'] for x in result['entries']] assert result == actions # now let's ignore some ledger actions for accounting response = requests.put( api_url_for( rotkehlchen_api_server, 'ignoredactionsresource', ), json={ 'action_type': 'ledger action', 'action_ids': ['3', '4'] }, # external ones ) result = assert_proper_response_with_result(response) assert result == { 'ledger action': [str(a['identifier']) for a in actions[0:2][::-1]] } # Now filter by location with json body response = requests.get( api_url_for( rotkehlchen_api_server, 'ledgeractionsresource', ), json={'location': 'external'}, ) result = assert_proper_response_with_result(response) result = result['entries'] assert all(x['ignored_in_accounting'] for x in result), 'all external should be ignored' result = [x['entry'] for x in result] assert result == actions[0:2] # Now filter by location with query param response = requests.get( api_url_for( rotkehlchen_api_server, 'ledgeractionsresource', ) + '?location=external', ) result = assert_proper_response_with_result(response) result = result['entries'] assert all(x['ignored_in_accounting'] for x in result), 'all external should be ignored' result = [x['entry'] for x in result] assert result == actions[0:2] # Now filter by time response = requests.get( api_url_for( rotkehlchen_api_server, 'ledgeractionsresource', ), json={ 'from_timestamp': 1, 'to_timestamp': 2 }, ) result = assert_proper_response_with_result(response) result = [x['entry'] for x in result['entries']] assert result == actions[2:4] # filter by both time and location response = requests.get( api_url_for( rotkehlchen_api_server, 'ledgeractionsresource', ), json={ 'from_timestamp': 2, 'to_timestamp': 3, 'location': 'blockchain' }, ) result = assert_proper_response_with_result(response) result = [x['entry'] for x in result['entries']] assert result == [actions[2]] # filter by ledger action type response = requests.get( api_url_for( rotkehlchen_api_server, 'ledgeractionsresource', ), json={'type': 'expense'}, ) result = assert_proper_response_with_result(response) result = [x['entry'] for x in result['entries']] assert result == [actions[2]] # filter by asset response = requests.get( api_url_for( rotkehlchen_api_server, 'ledgeractionsresource', ), json={'asset': 'EUR'}, ) result = assert_proper_response_with_result(response) result = [x['entry'] for x in result['entries']] assert result == [actions[0], actions[1]] # filter by asset with timestamp ascending order response = requests.get( api_url_for( rotkehlchen_api_server, 'ledgeractionsresource', ), json={ 'asset': 'EUR', 'ascending': True }, ) result = assert_proper_response_with_result(response) result = [x['entry'] for x in result['entries']] assert result == [actions[1], actions[0]] # test offset/limit pagination response = requests.get( api_url_for( rotkehlchen_api_server, 'ledgeractionsresource', ), json={ 'offset': 1, 'limit': 2 }, ) result = assert_proper_response_with_result(response) assert result['entries_found'] == 4 assert result['entries_total'] == 4 result = [x['entry'] for x in result['entries']] assert result == [actions[1], actions[2]] # test offset/limit pagination with a filter response = requests.get( api_url_for( rotkehlchen_api_server, 'ledgeractionsresource', ), json={ 'offset': 1, 'limit': 2, 'asset': 'EUR' }, ) result = assert_proper_response_with_result(response) assert result['entries_found'] == 2 assert result['entries_total'] == 4 result = [x['entry'] for x in result['entries']] assert result == [actions[1]] def assert_order_by(order_by: str): """A helper to keep things DRY in the test""" data = { 'order_by_attribute': order_by, 'ascending': False, 'only_cache': True } response = requests.get( api_url_for( rotkehlchen_api_server, 'ledgeractionsresource', ), json=data, ) result = assert_proper_response_with_result(response) assert result[ 'entries_limit'] == -1 if start_with_valid_premium else FREE_LEDGER_ACTIONS_LIMIT # noqa: E501 assert result['entries_total'] == 4 assert result['entries_found'] == 4 desc_result = result['entries'] assert len(desc_result) == 4 data = { 'order_by_attribute': order_by, 'ascending': True, 'only_cache': True } response = requests.get( api_url_for( rotkehlchen_api_server, 'ledgeractionsresource', ), json=data, ) result = assert_proper_response_with_result(response) assert result[ 'entries_limit'] == -1 if start_with_valid_premium else FREE_LEDGER_ACTIONS_LIMIT # noqa: E501 assert result['entries_total'] == 4 assert result['entries_found'] == 4 asc_result = result['entries'] assert len(asc_result) == 4 return desc_result, asc_result # test order by location desc_result, asc_result = assert_order_by('location') assert all(x['entry']['location'] == 'blockchain' for x in desc_result[:2]) assert all(x['entry']['location'] == 'external' for x in desc_result[2:]) assert all(x['entry']['location'] == 'external' for x in asc_result[:2]) assert all(x['entry']['location'] == 'blockchain' for x in asc_result[2:]) # test order by type desc_result, asc_result = assert_order_by('type') descending_types = [x['entry']['action_type'] for x in desc_result] assert [x['entry']['action_type'] for x in asc_result] == descending_types[::-1] # test order by amount desc_result, asc_result = assert_order_by('amount') for idx, x in enumerate(desc_result): if idx < len(desc_result) - 1: assert FVal(x['entry']['amount']) >= FVal( desc_result[idx + 1]['entry']['amount']) for idx, x in enumerate(asc_result): if idx < len(asc_result) - 1: assert FVal(x['entry']['amount']) <= FVal( asc_result[idx + 1]['entry']['amount']) # test order by rate desc_result, asc_result = assert_order_by('rate') for idx, x in enumerate(desc_result): if idx < len(desc_result) - 1: this = FVal( x['entry']['rate']) if x['entry']['rate'] is not None else ZERO next_ = FVal(desc_result[idx + 1]['entry']['rate']) if desc_result[ idx + 1]['entry']['rate'] is not None else ZERO # noqa: E501 assert this >= next_ for idx, x in enumerate(asc_result): if idx < len(asc_result) - 1: this = FVal( x['entry']['rate']) if x['entry']['rate'] is not None else ZERO next_ = FVal(asc_result[idx + 1]['entry']['rate']) if asc_result[ idx + 1]['entry']['rate'] is not None else ZERO # noqa: E501 assert this <= next_
def test_query_asset_movements_over_limit( rotkehlchen_api_server_with_exchanges, start_with_valid_premium, ): """Test that using the asset movements query endpoint works fine""" start_ts = 0 end_ts = 1598453214 server = rotkehlchen_api_server_with_exchanges rotki = server.rest_api.rotkehlchen # Make sure online kraken is not queried by setting query ranges rotki.data.db.update_used_query_range( name='kraken_asset_movements', start_ts=start_ts, end_ts=end_ts, ) polo_entries_num = 4 # Set a ton of kraken asset movements in the DB kraken_entries_num = FREE_ASSET_MOVEMENTS_LIMIT + 50 movements = [ AssetMovement(location=Location.KRAKEN, category=AssetMovementCategory.DEPOSIT, address=None, transaction_id=None, timestamp=x, asset=A_BTC, amount=FVal(x * 100), fee_asset=A_BTC, fee=FVal(x), link='') for x in range(kraken_entries_num) ] rotki.data.db.add_asset_movements(movements) all_movements_num = kraken_entries_num + polo_entries_num setup = prepare_rotki_for_history_processing_test( server.rest_api.rotkehlchen) # Check that querying movements with/without limits works even if we query two times for _ in range(2): # query asset movements of polo which has less movements than the limit with setup.polo_patch: response = requests.get( api_url_for( server, 'assetmovementsresource', ), json={'location': 'poloniex'}, ) result = assert_proper_response_with_result(response) assert result['entries_found'] == all_movements_num assert result[ 'entries_limit'] == -1 if start_with_valid_premium else FREE_ASSET_MOVEMENTS_LIMIT # noqa: E501 assert_poloniex_asset_movements( [x['entry'] for x in result['entries']], deserialized=True) # now query kraken which has a ton of DB entries response = requests.get( api_url_for(server, "assetmovementsresource"), json={'location': 'kraken'}, ) result = assert_proper_response_with_result(response) if start_with_valid_premium: assert len(result['entries']) == kraken_entries_num assert result['entries_limit'] == -1 assert result['entries_found'] == all_movements_num else: assert len(result['entries'] ) == FREE_ASSET_MOVEMENTS_LIMIT - polo_entries_num assert result['entries_limit'] == FREE_ASSET_MOVEMENTS_LIMIT assert result['entries_found'] == all_movements_num
def test_query_statistics_asset_balance_errors(rotkehlchen_api_server, rest_api_port): """Test that errors at the statistics asset balance over time endpoint are hanled properly""" start_time = ts_now() # Check that no asset given is an error response = requests.get( f'http://localhost:{rest_api_port}/api/1/statistics/balance') assert_error_response( response=response, status_code=HTTPStatus.NOT_FOUND, ) # Check that an invalid asset given is an error response = requests.get( api_url_for( rotkehlchen_api_server, "statisticsassetbalanceresource", asset="NOTAREALASSSETLOL", ), json={ 'from_timestamp': 0, 'to_timestamp': start_time }, ) assert_error_response( response=response, contained_in_msg='Unknown asset NOTAREALASSSETLOL provided', status_code=HTTPStatus.BAD_REQUEST, ) # Check that giving invalid value for from_timestamp is an error response = requests.get( api_url_for( rotkehlchen_api_server, "statisticsassetbalanceresource", asset="BTC", ), json={ 'from_timestamp': 'dsad', 'to_timestamp': start_time }, ) assert_error_response( response=response, contained_in_msg= 'Failed to deserialize a timestamp entry from string dsad', status_code=HTTPStatus.BAD_REQUEST, ) # Check that giving invalid value for to_timestamp is an error response = requests.get( api_url_for( rotkehlchen_api_server, "statisticsassetbalanceresource", asset="BTC", ), json={ 'from_timestamp': 0, 'to_timestamp': 53434.32 }, ) assert_error_response( response=response, contained_in_msg= '"Failed to deserialize a timestamp entry. Unexpected type', status_code=HTTPStatus.BAD_REQUEST, )
def test_set_settings_errors(rotkehlchen_api_server): """set settings errors and edge cases test""" rotki = rotkehlchen_api_server.rest_api.rotkehlchen # set timeout to 1 second to timeout faster rotki.blockchain.ethchain.eth_rpc_timeout = 1 # Eth rpc endpoint to which we can't connect data = { 'eth_rpc_endpoint': 'http://lol.com:5555', } response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data) assert_error_response( response=response, contained_in_msg='Failed to connect to ethereum node at endpoint', status_code=HTTPStatus.CONFLICT, ) # Invalid type for eth_rpc_endpoint data = { 'eth_rpc_endpoint': 5555, } response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data) assert_error_response( response=response, contained_in_msg='Not a valid string', status_code=HTTPStatus.BAD_REQUEST, ) # Invalid type for premium_should_sync data = { 'premium_should_sync': 444, } response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data) assert_error_response( response=response, contained_in_msg='Not a valid boolean', status_code=HTTPStatus.BAD_REQUEST, ) # Invalid type for include_crypto2crypto data = { 'include_crypto2crypto': 'ffdsdasd', } response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data) assert_error_response( response=response, contained_in_msg='Not a valid boolean', status_code=HTTPStatus.BAD_REQUEST, ) # Invalid type for anonymized_logs data = { 'anonymized_logs': 555.1, } response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data) assert_error_response( response=response, contained_in_msg='Not a valid boolean', status_code=HTTPStatus.BAD_REQUEST, ) # Invalid range for ui_floating_precision data = { 'ui_floating_precision': -1, } response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data) assert_error_response( response=response, contained_in_msg='Floating numbers precision in the UI must be between 0 and 8', status_code=HTTPStatus.BAD_REQUEST, ) data = { 'ui_floating_precision': 9, } response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data) assert_error_response( response=response, contained_in_msg='Floating numbers precision in the UI must be between 0 and 8', status_code=HTTPStatus.BAD_REQUEST, ) # Invalid type for ui_floating_precision data = { 'ui_floating_precision': 'dasdsds', } response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data) assert_error_response( response=response, contained_in_msg='Not a valid integer', status_code=HTTPStatus.BAD_REQUEST, ) # Invalid range for taxfree_after_period data = { 'taxfree_after_period': -2, } response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data) assert_error_response( response=response, contained_in_msg='Number of seconds after which taxfree period starts should not be negat', status_code=HTTPStatus.BAD_REQUEST, ) # Invalid type for taxfree_after_period data = { 'taxfree_after_period': 'dsad', } response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data) assert_error_response( response=response, contained_in_msg='Not a valid integer', status_code=HTTPStatus.BAD_REQUEST, ) # Invalid range for balance_save_frequency data = { 'balance_save_frequency': 0, } response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data) assert_error_response( response=response, contained_in_msg='The number of hours after which balances should be saved should be >= 1', status_code=HTTPStatus.BAD_REQUEST, ) # Invalid range for balance_save_frequency data = { 'balance_save_frequency': 'dasdsd', } response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data) assert_error_response( response=response, contained_in_msg='Not a valid integer', status_code=HTTPStatus.BAD_REQUEST, ) # Invalid type for include_gas_cost data = { 'include_gas_costs': 55.1, } response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data) assert_error_response( response=response, contained_in_msg='Not a valid boolean', status_code=HTTPStatus.BAD_REQUEST, ) # Invalid type for historical_data_start data = { 'historical_data_start': 12, } response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data) assert_error_response( response=response, contained_in_msg='Not a valid string', status_code=HTTPStatus.BAD_REQUEST, ) # Invalid asset for main currenty data = { 'main_currency': 'DSDSDSAD', } response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data) assert_error_response( response=response, contained_in_msg='Unknown asset DSDSDSAD', status_code=HTTPStatus.BAD_REQUEST, ) # non FIAT asset for main currency data = { 'main_currency': 'ETH', } response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data) assert_error_response( response=response, contained_in_msg='Asset ETH is not a FIAT asset', status_code=HTTPStatus.BAD_REQUEST, ) # invalid type main currency data = { 'main_currency': 243243, } response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data) assert_error_response( response=response, contained_in_msg='Tried to initialize an asset out of a non-string identifier', status_code=HTTPStatus.BAD_REQUEST, ) # invalid type date_display_format data = { 'date_display_format': 124.1, } response = requests.put(api_url_for(rotkehlchen_api_server, "settingsresource"), json=data) assert_error_response( response=response, contained_in_msg='Not a valid string', status_code=HTTPStatus.BAD_REQUEST, )
def test_query_statistics_asset_balance( rotkehlchen_api_server_with_exchanges, ethereum_accounts, btc_accounts, start_with_valid_premium, ): """Test that using the statistics asset balance over time 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 setup = setup_balances(rotki, ethereum_accounts, btc_accounts) # 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) # and now test that statistics work fine for ETH, with default time range (0 - now) response = requests.get( api_url_for( rotkehlchen_api_server_with_exchanges, "statisticsassetbalanceresource", asset="ETH", ), ) if start_with_valid_premium: result = assert_proper_response_with_result(response) assert len(result) == 1 entry = result[0] assert len(entry) == 4 assert FVal(entry['amount']) == get_asset_balance_total(A_ETH, setup) assert entry['category'] == 'asset' assert entry['time'] >= start_time assert entry['usd_value'] is not None 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 BTC, with given time range response = requests.get( api_url_for( rotkehlchen_api_server_with_exchanges, "statisticsassetbalanceresource", asset="BTC", ), json={ 'from_timestamp': 0, 'to_timestamp': start_time + 60000 }, ) if start_with_valid_premium: result = assert_proper_response_with_result(response) assert len(result) == 1 entry = result[0] assert len(entry) == 4 assert FVal(entry['amount']) == get_asset_balance_total(A_BTC, setup) assert entry['time'] >= start_time assert entry['category'] == 'asset' assert entry['usd_value'] is not None else: assert_error_response( response=response, contained_in_msg= 'logged in user testuser does not have a premium subscription', status_code=HTTPStatus.CONFLICT, ) # finally test that if the time range is not including the saved balances we get nothing back response = requests.get( api_url_for( rotkehlchen_api_server_with_exchanges, "statisticsassetbalanceresource", asset="BTC", ), json={ 'from_timestamp': 0, 'to_timestamp': start_time - 1 }, ) if start_with_valid_premium: result = assert_proper_response_with_result(response) assert len(result) == 0 else: assert_error_response( response=response, contained_in_msg= 'logged in user testuser does not have a premium subscription', status_code=HTTPStatus.CONFLICT, )
def test_set_settings(rotkehlchen_api_server): """Happy case settings modification test""" # Get the starting settings response = requests.get(api_url_for(rotkehlchen_api_server, "settingsresource")) assert_proper_response(response) json_data = response.json() original_settings = json_data['result'] assert json_data['message'] == '' # Create new settings which modify all of the original ones new_settings = {} unmodifiable_settings = ( 'version', 'last_write_ts', 'last_data_upload_ts', 'last_balance_save', ) for setting, value in original_settings.items(): if setting in unmodifiable_settings: continue elif setting == 'historical_data_start': value = '10/10/2016' elif setting == 'date_display_format': value = '%d/%m/%Y-%H:%M:%S' elif setting == 'eth_rpc_endpoint': value = 'http://working.nodes.com:8545' elif setting == 'main_currency': value = 'JPY' elif type(value) == bool: value = not value elif type(value) == int: value += 1 else: raise AssertionError(f'Unexpected settting {setting} encountered') new_settings[setting] = value # modify the settings block_query = patch('rotkehlchen.ethchain.Ethchain.query_eth_highest_block', return_value=0) mock_web3 = patch('rotkehlchen.ethchain.Web3', MockWeb3) with block_query, mock_web3: response = requests.put( api_url_for(rotkehlchen_api_server, "settingsresource"), json=new_settings, ) # Check that new settings are returned in the response assert_proper_response(response) json_data = response.json() assert json_data['message'] == '' result = json_data['result'] assert result['version'] == ROTKEHLCHEN_DB_VERSION for setting, value in new_settings.items(): msg = f'Error for {setting} setting. Expected: {value}. Got: {result[setting]}' assert result[setting] == value, msg # now check that the same settings are returned in a settings query response = requests.get(api_url_for(rotkehlchen_api_server, "settingsresource")) assert_proper_response(response) json_data = response.json() result = json_data['result'] assert json_data['message'] == '' for setting, value in new_settings.items(): assert result[setting] == value
def test_query_vaults_wbtc(rotkehlchen_api_server, ethereum_accounts): """Check vault info and details for a vault with WBTC as collateral""" rotki = rotkehlchen_api_server.rest_api.rotkehlchen proxies_mapping = { ethereum_accounts[0]: '0x9684e6C1c7B79868839b27F88bA6d5A176367075', # 8913 } mock_proxies(rotki, proxies_mapping) response = requests.get( api_url_for( rotkehlchen_api_server, "makerdaovaultsresource", )) # That proxy has 3 vaults. We only want to test 8913, which is closed/repaid so just keep that vaults = [ x for x in assert_proper_response_with_result(response) if x['identifier'] == 8913 ] vault_8913 = MakerDAOVault( identifier=8913, owner=ethereum_accounts[0], collateral_type='WBTC-A', urn='0x37f7B3C82A9Edc13FdCcE66E7d500b3698A13294', collateral_asset=A_WBTC, collateral_amount=ZERO, collateral_usd_value=ZERO, debt_value=ZERO, collateralization_ratio=None, liquidation_ratio=FVal(1.5), liquidation_price=None, stability_fee=FVal(0.01), ) expected_vaults = [vault_8913.serialize()] assert_serialized_lists_equal(expected_vaults, vaults) # And also make sure that the internal mapping will only query details of 8913 rotki.chain_manager.makerdao.vault_mappings = { ethereum_accounts[0]: [vault_8913] } response = requests.get( api_url_for( rotkehlchen_api_server, "makerdaovaultdetailsresource", )) vault_8913_details = { 'identifier': 8913, 'creation_ts': 1588664698, 'total_interest_owed': FVal('0.1903819198'), 'total_liquidated_amount': ZERO, 'total_liquidated_usd': ZERO, 'events': [{ 'event_type': 'deposit', 'amount': FVal('0.011'), 'amount_usd_value': FVal('87.06599'), 'timestamp': 1588664698, 'tx_hash': '0x9ba4a6187fa2c49ba327e7c923846a08a1e972017ec41d3f9f66ef524f7dde59', }, { 'event_type': 'generate', 'amount': FVal('25'), 'amount_usd_value': FVal('25.15'), 'timestamp': 1588664698, 'tx_hash': '0x9ba4a6187fa2c49ba327e7c923846a08a1e972017ec41d3f9f66ef524f7dde59', }, { 'event_type': 'payback', 'amount': FVal('25.000248996'), 'amount_usd_value': FVal('25.15025'), 'timestamp': 1588696496, 'tx_hash': '0x8bd960e7eb8b9e2b81d2446d1844dd63f94636c7800ea5e3b4d926ea0244c66c', }, { 'event_type': 'deposit', 'amount': FVal('0.0113'), 'amount_usd_value': FVal('89.440517'), 'timestamp': 1588720248, 'tx_hash': '0x678c4da562173c102473f1904ff293a767ebac9ec6c7d728ef2fd41acf00a13a', }], # way too many events in the vault, so no need to check them all } details = assert_proper_response_with_result(response) assert len(details) == 1 assert_serialized_dicts_equal( details[0], vault_8913_details, # Checking only the first 4 events length_list_keymap={'events': 4}, )
def test_add_and_query_ledger_actions(rotkehlchen_api_server): """Test that querying the ledger actions endpoint works as expected""" actions = _add_ledger_actions(rotkehlchen_api_server) response = requests.get( api_url_for( rotkehlchen_api_server, 'ledgeractionsresource', ), ) result = assert_proper_response_with_result(response) assert result['entries_found'] == 4 assert result['entries_limit'] == FREE_LEDGER_ACTIONS_LIMIT assert all(x['ignored_in_accounting'] is False for x in result['entries'] ), 'by default nothing should be ignored' # noqa: E501 result = [x['entry'] for x in result['entries']] assert result == actions # now let's ignore some ledger actions for accounting response = requests.put( api_url_for( rotkehlchen_api_server, 'ignoredactionsresource', ), json={ 'action_type': 'ledger action', 'action_ids': ['3', '4'] }, # external ones ) result = assert_proper_response_with_result(response) assert result == { 'ledger action': [str(a['identifier']) for a in actions[2:]] } # Now filter by location with json body response = requests.get( api_url_for( rotkehlchen_api_server, "ledgeractionsresource", ), json={'location': 'external'}, ) result = assert_proper_response_with_result(response) result = result['entries'] assert all(x['ignored_in_accounting'] for x in result), 'all external should be ignored' result = [x['entry'] for x in result] assert result == actions[2:] # Now filter by location with query param response = requests.get( api_url_for( rotkehlchen_api_server, "ledgeractionsresource", ) + '?location=external', ) result = assert_proper_response_with_result(response) result = result['entries'] assert all(x['ignored_in_accounting'] for x in result), 'all external should be ignored' result = [x['entry'] for x in result] assert result == actions[2:] # Now filter by time response = requests.get( api_url_for( rotkehlchen_api_server, "ledgeractionsresource", ), json={ 'from_timestamp': 1, 'to_timestamp': 2 }, ) result = assert_proper_response_with_result(response) result = [x['entry'] for x in result['entries']] assert result == actions[:2] # and finally filter by both time and location response = requests.get( api_url_for( rotkehlchen_api_server, "ledgeractionsresource", ), json={ 'from_timestamp': 2, 'to_timestamp': 3, 'location': 'blockchain' }, ) result = assert_proper_response_with_result(response) result = [x['entry'] for x in result['entries']] assert result == actions[1:2]
def test_two_vaults_same_account_same_collateral(rotkehlchen_api_server, ethereum_accounts): """Check that no events are duplicated between vaults for same collateral by same account Test for vaults side of https://github.com/rotki/rotki/issues/1032 """ rotki = rotkehlchen_api_server.rest_api.rotkehlchen proxies_mapping = { # proxy for 8632 and 8543 ethereum_accounts[0]: '0xAe9996b76bdAa003ace6D66328A6942565f5768d', } mock_proxies(rotki, proxies_mapping) response = requests.get( api_url_for( rotkehlchen_api_server, "makerdaovaultsresource", )) vaults = assert_proper_response_with_result(response) vault_8543 = { 'identifier': 8543, 'owner': ethereum_accounts[0], 'collateral_type': 'ETH-A', 'collateral_asset': 'ETH', 'collateral_amount': ZERO, 'collateral_usd_value': ZERO, 'debt_value': ZERO, 'collateralization_ratio': None, 'liquidation_ratio': '150.00%', 'liquidation_price': None, 'stability_fee': '0.00%', } vault_8632 = { 'identifier': 8632, 'owner': ethereum_accounts[0], 'collateral_type': 'ETH-A', 'collateral_asset': 'ETH', 'collateral_amount': '0.0', 'collateral_usd_value': '0.0', 'debt_value': '0.0', 'collateralization_ratio': None, 'liquidation_ratio': '150.00%', 'liquidation_price': None, 'stability_fee': '0.00%', } assert len(vaults) == 2 assert_serialized_dicts_equal(vaults[0], vault_8543) assert_serialized_dicts_equal(vaults[1], vault_8632, ignore_keys=VAULT_IGNORE_KEYS) response = requests.get( api_url_for( rotkehlchen_api_server, "makerdaovaultdetailsresource", )) vault_8543_details = { 'identifier': 8543, 'creation_ts': 1587910979, 'total_interest_owed': ZERO, 'total_liquidated_amount': ZERO, 'total_liquidated_usd': ZERO, 'events': [{ 'event_type': 'deposit', 'amount': FVal('1'), 'amount_usd_value': FVal('197.78'), 'timestamp': 1587910979, 'tx_hash': '0xf59858df4e42cdc2aecfebdcf38e1df841866c6a9eb3adb6bde9a844564a3bb6', }, { 'event_type': 'generate', 'amount': FVal('80'), 'amount_usd_value': FVal('81.2'), 'timestamp': 1587910979, 'tx_hash': '0xf59858df4e42cdc2aecfebdcf38e1df841866c6a9eb3adb6bde9a844564a3bb6', }, { 'event_type': 'payback', 'amount': FVal('80'), 'amount_usd_value': FVal('80.24'), 'timestamp': 1589989097, 'tx_hash': '0x52396f7d20db54e2e9e716698b643a39815ff149a6cccbe9c7597dc9e06bb9d3', }, { 'event_type': 'deposit', 'amount': FVal('3.5'), 'amount_usd_value': FVal('734.475'), 'timestamp': 1589993538, 'tx_hash': '0x3c3942dc40fe68303098d91e765ceecaed4664bba0ef8f8e684b6f0e61968c6c', }, { 'event_type': 'withdraw', 'amount': FVal('4.5'), 'amount_usd_value': FVal('893.52'), 'timestamp': 1590043499, 'tx_hash': '0xbcd4158f0089404f6ab5378517762cddc13d21c9d2fcf3fd45cf1cf4b656242c', }], } vault_8632_details = { 'identifier': 8632, 'creation_ts': 1588174425, 'total_interest_owed': ZERO, 'total_liquidated_amount': ZERO, 'total_liquidated_usd': ZERO, 'events': [{ 'event_type': 'deposit', 'amount': FVal('2.4'), 'amount_usd_value': FVal('517.32'), 'timestamp': 1588174425, 'tx_hash': '0xdb677a4257b5bdb305c278102d7b2460408bb7a3981414b994f4dd80a737ac2a', }, { 'event_type': 'generate', 'amount': FVal('192'), 'amount_usd_value': FVal('194.688'), 'timestamp': 1588174425, 'tx_hash': '0xdb677a4257b5bdb305c278102d7b2460408bb7a3981414b994f4dd80a737ac2a', }, { 'event_type': 'payback', 'amount': FVal('192'), 'amount_usd_value': FVal('192.192'), 'timestamp': 1590042891, 'tx_hash': '0x488a937677030cc810d0062001c08c944ecf6329b24a45ae9480bada8147bf75', }, { 'event_type': 'deposit', 'amount': FVal('4.4'), 'amount_usd_value': FVal('873.664'), 'timestamp': 1590043699, 'tx_hash': '0x712ddb654b878bcb30c5344d7c18f7f796fe94abd6e5b8a22b2da0a6c99bb425', }, { 'event_type': 'generate', 'amount': FVal('429.79'), 'amount_usd_value': FVal('430.21979'), 'timestamp': 1590044118, 'tx_hash': '0x36bfa27e157c03393a8816f6c1e3e990474f8f7473413810d87e2f4981d58044', }], } details = assert_proper_response_with_result(response) assert len(details) == 2 assert_serialized_dicts_equal(details[0], vault_8543_details) assert_serialized_dicts_equal( details[1], vault_8632_details, ignore_keys=[ 'total_interest_owed', 'total_liquidated_amount', 'total_liquidated_usd' ], # Checking only the first 5 events, since that's how many we had when the test was written length_list_keymap={'events': 5}, )
def test_add_and_query_manually_tracked_balances( rotkehlchen_api_server, ethereum_accounts, ): """Test that adding and querying manually tracked balances via the API works fine""" async_query = random.choice([False, True]) rotki = rotkehlchen_api_server.rest_api.rotkehlchen setup = setup_balances(rotki, ethereum_accounts=ethereum_accounts, btc_accounts=None) _populate_tags(rotkehlchen_api_server) response = requests.get( api_url_for( rotkehlchen_api_server, "manuallytrackedbalancesresource", ), json={'async_query': async_query}, ) if async_query: task_id = assert_ok_async_response(response) outcome = wait_for_async_task(rotkehlchen_api_server, task_id) result = outcome['result'] else: result = assert_proper_response_with_result(response) assert result['balances'] == [], 'In the beginning we should have no entries' balances = _populate_initial_balances(rotkehlchen_api_server) # now query and make sure the added balances are returned response = requests.get( api_url_for( rotkehlchen_api_server, "manuallytrackedbalancesresource", ), json={'async_query': async_query}, ) if async_query: task_id = assert_ok_async_response(response) outcome = wait_for_async_task(rotkehlchen_api_server, task_id) result = outcome['result'] else: result = assert_proper_response_with_result(response) assert_balances_match(expected_balances=balances, returned_balances=result['balances']) now = ts_now() # Also now test for https://github.com/rotki/rotki/issues/942 by querying for all balances # causing all balances to be saved and making sure the manual balances also got saved with ExitStack() as stack: setup.enter_ethereum_patches(stack) response = requests.get( api_url_for( rotkehlchen_api_server, "allbalancesresource", ), json={'async_query': async_query}, ) if async_query: task_id = assert_ok_async_response(response) outcome = wait_for_async_task(rotkehlchen_api_server, task_id) result = outcome['result'] else: result = assert_proper_response_with_result(response) result = result['assets'] assert result['BTC']['amount'] == '1.425' assert result['XMR']['amount'] == '50.315' assert result[A_BNB.identifier]['amount'] == '155' # Check DB to make sure a save happened assert rotki.data.db.get_last_balance_save_time() >= now assert set(rotki.data.db.query_owned_assets()) == { 'BTC', 'XMR', A_BNB.identifier, 'ETH', A_RDN.identifier, }
def test_get_events( rotkehlchen_api_server, ethereum_accounts, # pylint: disable=unused-argument rotki_premium_credentials, # pylint: disable=unused-argument start_with_valid_premium, # pylint: disable=unused-argument ): async_query = random.choice([False, True]) rotki = rotkehlchen_api_server.rest_api.rotkehlchen # Set module premium is required for calling `get_balances()` premium = None if start_with_valid_premium: premium = Premium(rotki_premium_credentials) rotki.chain_manager.adex.premium = premium setup = setup_balances( rotki, ethereum_accounts=ethereum_accounts, btc_accounts=None, original_queries=['zerion', 'logs', 'blocknobytime'], ) with ExitStack() as stack: # patch ethereum/etherscan to not autodetect tokens setup.enter_ethereum_patches(stack) response = requests.get( api_url_for(rotkehlchen_api_server, 'adexhistoryresource'), json={ 'async_query': async_query, 'to_timestamp': 1611747322 }, ) if async_query: task_id = assert_ok_async_response(response) outcome = wait_for_async_task(rotkehlchen_api_server, task_id) assert outcome['message'] == '' result = outcome['result'] else: result = assert_proper_response_with_result(response) identity_address = '0x2a6c38D16BFdc7b4a20f1F982c058F07BDCe9204' tom_pool_id = '0x2ce0c96383fb229d9776f33846e983a956a7d95844fac57b180ed0071d93bb28' bond_id = '0x540cab9883923c01e657d5da4ca5674b6e4626b4a148224635495502d674c7c5' result = result[ADEX_TEST_ADDR] expected_events = [ Bond( tx_hash= '0x9989f47c6c0a761f98f910ac24e2438d858be96c12124a13be4bb4b3150c55ea', address=ADEX_TEST_ADDR, identity_address=identity_address, timestamp=1604366004, bond_id=bond_id, pool_id=tom_pool_id, value=Balance(FVal(100000), FVal(200000)), nonce=0, slashed_at=0, ), ChannelWithdraw( tx_hash= '0xa9ee91af823c0173fc5ada908ff9fe3f4d7c84a2c9da795f0889b3f4ace75b13', address=ADEX_TEST_ADDR, identity_address=identity_address, timestamp=1607453764, channel_id='', pool_id=tom_pool_id, value=Balance(FVal('5056.894263641728544592'), FVal('10113.788527283457089184')), token=A_ADX, ), Unbond( tx_hash= '0xa9ee91af823c0173fc5ada908ff9fe3f4d7c84a2c9da795f0889b3f4ace75b13', address=ADEX_TEST_ADDR, identity_address=identity_address, timestamp=1607453764, bond_id=bond_id, pool_id=tom_pool_id, value=Balance(FVal(100000), FVal(200000)), ) ] assert len(result['events']) == 8 assert result['events'][:len(expected_events)] == [ x.serialize() for x in expected_events ] assert 'staking_details' in result # Make sure events end up in the DB assert len(rotki.data.db.get_adex_events()) != 0 # test adex data purging from the db works response = requests.delete( api_url_for( rotkehlchen_api_server, 'namedethereummoduledataresource', module_name='adex', )) assert_simple_ok_response(response) assert len(rotki.data.db.get_adex_events()) == 0
def test_add_edit_manually_tracked_balances_errors( rotkehlchen_api_server, verb, ): """Test that errors in input data while adding/editing manually tracked balances are handled properly""" _populate_tags(rotkehlchen_api_server) balances = {'balances': [{ "asset": "XMR", "label": "My monero wallet", "amount": "50.315", "tags": ["public", "mInEr"], "location": "blockchain", }, { "asset": "BTC", "label": "My XPUB BTC wallet", "amount": "1.425", "location": "blockchain", }]} # invalid initial input type response = requests.request( verb, api_url_for( rotkehlchen_api_server, 'manuallytrackedbalancesresource', ), json=[1, 2, 3], ) assert_error_response( response=response, contained_in_msg='Invalid input type', status_code=HTTPStatus.BAD_REQUEST, ) # missing balances response = requests.request( verb, api_url_for( rotkehlchen_api_server, "manuallytrackedbalancesresource", ), json={'foo': 1}, ) assert_error_response( response=response, contained_in_msg='balances": ["Missing data for required field', status_code=HTTPStatus.BAD_REQUEST, ) # wrong type for balances response = requests.request( verb, api_url_for( rotkehlchen_api_server, "manuallytrackedbalancesresource", ), json={'balances': 'foo'}, ) assert_error_response( response=response, contained_in_msg='balances": ["Not a valid list', status_code=HTTPStatus.BAD_REQUEST, ) # missing asset entry data = deepcopy(balances) del data['balances'][0]['asset'] response = requests.request( verb, api_url_for( rotkehlchen_api_server, "manuallytrackedbalancesresource", ), json=data, ) assert_error_response( response=response, contained_in_msg='Missing data for required field', status_code=HTTPStatus.BAD_REQUEST, ) # invalid type for asset data = deepcopy(balances) data['balances'][0]['asset'] = 123 response = requests.request( verb, api_url_for( rotkehlchen_api_server, "manuallytrackedbalancesresource", ), json=data, ) assert_error_response( response=response, contained_in_msg='Tried to initialize an asset out of a non-string identifier', status_code=HTTPStatus.BAD_REQUEST, ) # unknown asset data = deepcopy(balances) data['balances'][0]['asset'] = 'SDSFFGFA' response = requests.request( verb, api_url_for( rotkehlchen_api_server, "manuallytrackedbalancesresource", ), json=data, ) assert_error_response( response=response, contained_in_msg='Unknown asset SDSFFGFA provided', status_code=HTTPStatus.BAD_REQUEST, ) # missing label entry data = deepcopy(balances) del data['balances'][0]['label'] response = requests.request( verb, api_url_for( rotkehlchen_api_server, "manuallytrackedbalancesresource", ), json=data, ) assert_error_response( response=response, contained_in_msg='Missing data for required field', status_code=HTTPStatus.BAD_REQUEST, ) # wrong type for label data = deepcopy(balances) data['balances'][0]['label'] = 55 response = requests.request( verb, api_url_for( rotkehlchen_api_server, "manuallytrackedbalancesresource", ), json=data, ) assert_error_response( response=response, contained_in_msg='label": ["Not a valid string', status_code=HTTPStatus.BAD_REQUEST, ) # missing amount entry data = deepcopy(balances) del data['balances'][0]['amount'] response = requests.request( verb, api_url_for( rotkehlchen_api_server, "manuallytrackedbalancesresource", ), json=data, ) assert_error_response( response=response, contained_in_msg='Missing data for required field', status_code=HTTPStatus.BAD_REQUEST, ) # wrong type for amount data = deepcopy(balances) data['balances'][0]['amount'] = 'gra' response = requests.request( verb, api_url_for( rotkehlchen_api_server, "manuallytrackedbalancesresource", ), json=data, ) assert_error_response( response=response, contained_in_msg='Failed to deserialize an amount entry', status_code=HTTPStatus.BAD_REQUEST, ) # missing location entry data = deepcopy(balances) del data['balances'][0]['location'] response = requests.request( verb, api_url_for( rotkehlchen_api_server, "manuallytrackedbalancesresource", ), json=data, ) assert_error_response( response=response, contained_in_msg='Missing data for required field', status_code=HTTPStatus.BAD_REQUEST, ) # wrong type for location data = deepcopy(balances) data['balances'][0]['location'] = 55 response = requests.request( verb, api_url_for( rotkehlchen_api_server, 'manuallytrackedbalancesresource', ), json=data, ) assert_error_response( response=response, contained_in_msg='Failed to deserialize Location value from non string value', status_code=HTTPStatus.BAD_REQUEST, ) # invalid location data = deepcopy(balances) data['balances'][0]['location'] = 'foo' response = requests.request( verb, api_url_for( rotkehlchen_api_server, 'manuallytrackedbalancesresource', ), json=data, ) assert_error_response( response=response, contained_in_msg='Failed to deserialize Location value foo', status_code=HTTPStatus.BAD_REQUEST, ) # wrong type for tags data = deepcopy(balances) data['balances'][0]['tags'] = 55 response = requests.request( verb, api_url_for( rotkehlchen_api_server, 'manuallytrackedbalancesresource', ), json=data, ) assert_error_response( response=response, contained_in_msg='tags": ["Not a valid list', status_code=HTTPStatus.BAD_REQUEST, ) # wrong type in list of tags data = deepcopy(balances) data['balances'][0]['tags'] = ['foo', 55] response = requests.request( verb, api_url_for( rotkehlchen_api_server, 'manuallytrackedbalancesresource', ), json=data, ) assert_error_response( response=response, contained_in_msg='"tags": {"1": ["Not a valid string."', status_code=HTTPStatus.BAD_REQUEST, )
def test_user_creation_errors(rotkehlchen_api_server, data_dir): """Test errors and edge cases for user creation""" # Missing username username = '******' data = { 'password': '******', } response = requests.put(api_url_for(rotkehlchen_api_server, "usersresource"), json=data) assert_error_response( response=response, contained_in_msg='Missing data for required field', ) # Missing password username = '******' data = { 'name': username, } response = requests.put(api_url_for(rotkehlchen_api_server, "usersresource"), json=data) assert_error_response( response=response, contained_in_msg='Missing data for required field', ) # Invalid type for name data = { 'name': 5435345.31, 'password': '******', } response = requests.put(api_url_for(rotkehlchen_api_server, "usersresource"), json=data) assert_error_response( response=response, contained_in_msg='Not a valid string', ) # Invalid type for password data = { 'name': username, 'password': 4535, } response = requests.put(api_url_for(rotkehlchen_api_server, "usersresource"), json=data) assert_error_response( response=response, contained_in_msg='Not a valid string', ) # Provide only premium_api_key data = { 'name': username, 'password': '******', 'premium_api_key': 'asdsada', } response = requests.put(api_url_for(rotkehlchen_api_server, "usersresource"), json=data) assert_error_response( response=response, contained_in_msg='Must provide both or neither of api key/secret', ) # Provide only premium_api_secret data = { 'name': username, 'password': '******', 'premium_api_secret': 'asdsada', } response = requests.put(api_url_for(rotkehlchen_api_server, "usersresource"), json=data) assert_error_response( response=response, contained_in_msg='Must provide both or neither of api key/secret', ) # Invalid type for premium api key data = { 'name': username, 'password': '******', 'premium_api_key': True, } response = requests.put(api_url_for(rotkehlchen_api_server, "usersresource"), json=data) assert_error_response( response=response, contained_in_msg='Not a valid string', ) # Invalid type for premium api secret data = { 'name': username, 'password': '******', 'premium_api_secret': 45.2, } response = requests.put(api_url_for(rotkehlchen_api_server, "usersresource"), json=data) assert_error_response( response=response, contained_in_msg='Not a valid string', ) # Check that the directory was NOT created assert not Path(data_dir / username / 'rotkehlchen.db').exists() # Let's pretend there is another user, and try to create them again Path(data_dir / 'another_user').mkdir() Path(data_dir / 'another_user' / 'rotkehlchen.db').touch() data = { 'name': 'another_user', 'password': '******', } response = requests.put(api_url_for(rotkehlchen_api_server, "usersresource"), json=data) assert_error_response( response=response, contained_in_msg='User another_user already exists', status_code=HTTPStatus.CONFLICT, )
def test_delete_manually_tracked_balances_errors(rotkehlchen_api_server): """Test that errors at deleting manually tracked balances in the API are handled""" _populate_tags(rotkehlchen_api_server) _populate_initial_balances(rotkehlchen_api_server) # invalid initial input type response = requests.delete( api_url_for( rotkehlchen_api_server, "manuallytrackedbalancesresource", ), json=[], ) assert_error_response( response=response, contained_in_msg="Invalid input type", status_code=HTTPStatus.BAD_REQUEST, ) # missing labels response = requests.delete( api_url_for( rotkehlchen_api_server, "manuallytrackedbalancesresource", ), json={}, ) assert_error_response( response=response, contained_in_msg='labels": ["Missing data for required field', status_code=HTTPStatus.BAD_REQUEST, ) # wrong type for labels response = requests.delete( api_url_for( rotkehlchen_api_server, "manuallytrackedbalancesresource", ), json={'labels': 1}, ) assert_error_response( response=response, contained_in_msg='"labels": ["Not a valid list', status_code=HTTPStatus.BAD_REQUEST, ) # wrong type for label entries response = requests.delete( api_url_for( rotkehlchen_api_server, "manuallytrackedbalancesresource", ), json={'labels': ['My monero wallet', 55]}, ) assert_error_response( response=response, contained_in_msg='"labels": {"1": ["Not a valid string."', status_code=HTTPStatus.BAD_REQUEST, ) # delete non-existing label response = requests.delete( api_url_for( rotkehlchen_api_server, "manuallytrackedbalancesresource", ), json={'labels': ['My monero wallet', 'nonexisting']}, ) assert_error_response( response=response, contained_in_msg='Tried to remove 1 manually tracked balance labels that do not exist', status_code=HTTPStatus.BAD_REQUEST, )
def test_user_login(rotkehlchen_api_server, username, db_password, data_dir): """Test that user login works properly""" rotki = rotkehlchen_api_server.rest_api.rotkehlchen # Let's pretend there is another user, and try to create them again Path(data_dir / 'another_user').mkdir() Path(data_dir / 'another_user' / 'rotkehlchen.db').touch() # Check users status users_data = check_user_status(rotkehlchen_api_server) assert len(users_data) == 2 assert users_data[username] == 'loggedin' assert users_data['another_user'] == 'loggedout' # Logout of the active user data = {'action': 'logout'} response = requests.patch( api_url_for(rotkehlchen_api_server, "usersbynameresource", name=username), json=data, ) assert_simple_ok_response(response) assert rotki.user_is_logged_in is False users_data = check_user_status(rotkehlchen_api_server) assert len(users_data) == 2 assert users_data[username] == 'loggedout' assert users_data['another_user'] == 'loggedout' # Now let's try to login data = { 'action': 'login', "password": db_password, 'sync_approval': 'unknown' } response = requests.patch( api_url_for(rotkehlchen_api_server, "usersbynameresource", name=username), json=data, ) # And make sure it works assert_proper_response(response) check_proper_unlock_result(response.json()) assert rotki.user_is_logged_in is True users_data = check_user_status(rotkehlchen_api_server) assert len(users_data) == 2 assert users_data[username] == 'loggedin' assert users_data['another_user'] == 'loggedout' # Logout again data = {'action': 'logout'} response = requests.patch( api_url_for(rotkehlchen_api_server, "usersbynameresource", name=username), json=data, ) assert_simple_ok_response(response) assert rotki.user_is_logged_in is False users_data = check_user_status(rotkehlchen_api_server) assert len(users_data) == 2 assert users_data[username] == 'loggedout' assert users_data['another_user'] == 'loggedout' # Now try to login with a wrong password data = { 'action': 'login', "password": '******', 'sync_approval': 'unknown' } response = requests.patch( api_url_for(rotkehlchen_api_server, "usersbynameresource", name=username), json=data, ) # And make sure it fails assert_error_response( response=response, contained_in_msg='Wrong password or invalid/corrupt database for user', status_code=HTTPStatus.UNAUTHORIZED, ) users_data = check_user_status(rotkehlchen_api_server) assert len(users_data) == 2 assert users_data[username] == 'loggedout' assert users_data['another_user'] == 'loggedout' # Now let's manually add valid but not authenticable premium credentials in the DB data = { 'action': 'login', "password": db_password, 'sync_approval': 'unknown' } response = requests.patch( api_url_for(rotkehlchen_api_server, "usersbynameresource", name=username), json=data, ) credentials = PremiumCredentials(VALID_PREMIUM_KEY, VALID_PREMIUM_SECRET) rotki.data.db.set_rotkehlchen_premium(credentials) data = {'action': 'logout'} response = requests.patch( api_url_for(rotkehlchen_api_server, "usersbynameresource", name=username), json=data, ) assert_simple_ok_response(response) assert rotki.user_is_logged_in is False # And try to login while having these unauthenticable premium credentials in the DB data = { 'action': 'login', "password": db_password, 'sync_approval': 'unknown' } response = requests.patch( api_url_for(rotkehlchen_api_server, "usersbynameresource", name=username), json=data, ) # And make sure it works despite having unauthenticable premium credentials in the DB assert_proper_response(response) check_proper_unlock_result(response.json()) assert rotki.user_is_logged_in is True users_data = check_user_status(rotkehlchen_api_server) assert len(users_data) == 2 assert users_data[username] == 'loggedin' assert users_data['another_user'] == 'loggedout'
def test_users_by_name_endpoint_errors(rotkehlchen_api_server, username, db_password): """Test that user by name endpoint errors are handled (for login/logout and edit)""" rotki = rotkehlchen_api_server.rest_api.rotkehlchen # Now let's try to login while the user is already logged in data = { 'action': 'login', 'password': db_password, 'sync_approval': 'unknown' } response = requests.patch( api_url_for(rotkehlchen_api_server, "usersbynameresource", name=username), json=data, ) expected_msg = ( f'Can not login to user {username} because user {username} is ' f'already logged in. Log out of that user first') assert_error_response( response=response, contained_in_msg=expected_msg, status_code=HTTPStatus.CONFLICT, ) assert rotki.user_is_logged_in is True # Logout of the active user data = {'action': 'logout'} response = requests.patch( api_url_for(rotkehlchen_api_server, "usersbynameresource", name=username), json=data, ) assert_simple_ok_response(response) assert rotki.user_is_logged_in is False # Now let's try to login with an invalid password data = { 'action': 'login', 'password': '******', 'sync_approval': 'unknown' } response = requests.patch( api_url_for(rotkehlchen_api_server, "usersbynameresource", name=username), json=data, ) assert_error_response( response=response, contained_in_msg='Wrong password or invalid/corrupt database for user', status_code=HTTPStatus.UNAUTHORIZED, ) assert rotki.user_is_logged_in is False # Login action without a password data = {'action': 'login', 'sync_approval': 'unknown'} response = requests.patch( api_url_for(rotkehlchen_api_server, "usersbynameresource", name=username), json=data, ) assert_error_response( response=response, contained_in_msg='Missing password field for login', status_code=HTTPStatus.BAD_REQUEST, ) assert rotki.user_is_logged_in is False # No action and no premium credentials data = {'sync_approval': 'unknown'} response = requests.patch( api_url_for(rotkehlchen_api_server, "usersbynameresource", name=username), json=data, ) assert_error_response( response=response, contained_in_msg= 'Without an action premium api key and secret must be provided', status_code=HTTPStatus.BAD_REQUEST, ) assert rotki.user_is_logged_in is False # No action and only premium key data = {'sync_approval': 'unknown', 'premium_api_key': VALID_PREMIUM_KEY} response = requests.patch( api_url_for(rotkehlchen_api_server, "usersbynameresource", name=username), json=data, ) assert_error_response( response=response, contained_in_msg= 'Without an action premium api key and secret must be provided', status_code=HTTPStatus.BAD_REQUEST, ) assert rotki.user_is_logged_in is False # No action and only premium secret data = { 'sync_approval': 'unknown', 'premium_api_secret': VALID_PREMIUM_SECRET } response = requests.patch( api_url_for(rotkehlchen_api_server, "usersbynameresource", name=username), json=data, ) assert_error_response( response=response, contained_in_msg= 'Without an action premium api key and secret must be provided', status_code=HTTPStatus.BAD_REQUEST, ) assert rotki.user_is_logged_in is False # Invalid action type data = { 'action': 555.3, 'password': db_password, 'sync_approval': 'unknown' } response = requests.patch( api_url_for(rotkehlchen_api_server, "usersbynameresource", name=username), json=data, ) assert_error_response( response=response, contained_in_msg='Not a valid string', status_code=HTTPStatus.BAD_REQUEST, ) assert rotki.user_is_logged_in is False # Invalid action string data = { 'action': 'chopwood', 'password': db_password, 'sync_approval': 'unknown' } response = requests.patch( api_url_for(rotkehlchen_api_server, "usersbynameresource", name=username), json=data, ) assert_error_response( response=response, contained_in_msg='Must be one of: login, logout', status_code=HTTPStatus.BAD_REQUEST, ) assert rotki.user_is_logged_in is False # Invalid password type data = {'action': 'login', 'password': True, 'sync_approval': 'unknown'} response = requests.patch( api_url_for(rotkehlchen_api_server, "usersbynameresource", name=username), json=data, ) assert_error_response( response=response, contained_in_msg='Not a valid string', status_code=HTTPStatus.BAD_REQUEST, ) assert rotki.user_is_logged_in is False