From 0a79157dc6de48fc4a5ced37de36863c2c13521c Mon Sep 17 00:00:00 2001 From: everoddandeven Date: Sun, 4 Jan 2026 18:55:01 +0100 Subject: [PATCH] Enable wallet keys tests --- src/cpp/daemon/py_monero_daemon_model.cpp | 2 +- src/cpp/wallet/py_monero_wallet.cpp | 5 +- src/cpp/wallet/py_monero_wallet_model.cpp | 2 +- tests/test_monero_daemon_rpc.py | 6 +- tests/test_monero_utils.py | 2 +- tests/test_monero_wallet_common.py | 13 +- tests/test_monero_wallet_keys.py | 284 +++++++++++----------- tests/utils/monero_test_utils.py | 79 +++--- 8 files changed, 196 insertions(+), 197 deletions(-) diff --git a/src/cpp/daemon/py_monero_daemon_model.cpp b/src/cpp/daemon/py_monero_daemon_model.cpp index c296d43..6e23969 100644 --- a/src/cpp/daemon/py_monero_daemon_model.cpp +++ b/src/cpp/daemon/py_monero_daemon_model.cpp @@ -1269,7 +1269,7 @@ void PyMoneroConnectionManager::add_listener(const std::shared_ptr &listener) { boost::lock_guard lock(m_listeners_mutex); - std::remove_if(m_listeners.begin(), m_listeners.end(), [&listener](std::shared_ptr iter){ return iter == listener; }), m_listeners.end(); + m_listeners.erase(std::remove_if(m_listeners.begin(), m_listeners.end(), [&listener](std::shared_ptr iter){ return iter == listener; }), m_listeners.end()); } void PyMoneroConnectionManager::remove_listeners() { diff --git a/src/cpp/wallet/py_monero_wallet.cpp b/src/cpp/wallet/py_monero_wallet.cpp index ba34a64..ac9f72a 100644 --- a/src/cpp/wallet/py_monero_wallet.cpp +++ b/src/cpp/wallet/py_monero_wallet.cpp @@ -709,7 +709,6 @@ void PyMoneroWalletRpc::rescan_blockchain() { uint64_t PyMoneroWalletRpc::get_balance() const { auto wallet_balance = get_balances(boost::none, boost::none); return wallet_balance->m_balance; - return 0; } uint64_t PyMoneroWalletRpc::get_balance(uint32_t account_index) const { @@ -745,11 +744,11 @@ std::vector PyMoneroWalletRpc::get_accounts(bool include_subaddr auto node = response->m_result.get(); std::vector accounts; PyMoneroAccount::from_property_tree(node, accounts); - std::vector empty_indices; if (include_subaddresses) { for (auto &account : accounts) { + std::vector empty_indices; account.m_subaddresses = get_subaddresses(account.m_index.get(), empty_indices, true); if (!skip_balances) { @@ -1472,7 +1471,7 @@ std::shared_ptr PyMoneroWalletRpc::parse_payment_uri(const std void PyMoneroWalletRpc::set_attribute(const std::string& key, const std::string& val) { auto params = std::make_shared(key, val); PyMoneroJsonRequest request("set_attribute", params); - std::shared_ptr response = m_rpc->send_json_request(request); + m_rpc->send_json_request(request); } bool PyMoneroWalletRpc::get_attribute(const std::string& key, std::string& value) const { diff --git a/src/cpp/wallet/py_monero_wallet_model.cpp b/src/cpp/wallet/py_monero_wallet_model.cpp index 56134be..fc06436 100644 --- a/src/cpp/wallet/py_monero_wallet_model.cpp +++ b/src/cpp/wallet/py_monero_wallet_model.cpp @@ -193,7 +193,7 @@ void PyMoneroTxWallet::from_property_tree_with_transfer(const boost::property_tr if (outgoing_transfer == nullptr) outgoing_transfer = std::make_shared(); incoming_transfer->m_amount = it->second.get_value(); } - else if (!is_outgoing) { + else { if (incoming_transfer == nullptr) incoming_transfer = std::make_shared(); incoming_transfer->m_tx = tx; incoming_transfer->m_amount = it->second.get_value(); diff --git a/tests/test_monero_daemon_rpc.py b/tests/test_monero_daemon_rpc.py index 6bdcc07..57fa319 100644 --- a/tests/test_monero_daemon_rpc.py +++ b/tests/test_monero_daemon_rpc.py @@ -298,7 +298,7 @@ def test_get_txs_by_hashes(self) -> None: # fetch tx hashses to test tx_hashes = Utils.get_confirmed_tx_hashes(self._daemon) assert len(tx_hashes) > 0, "No tx hashes found" - + # context for creating txs ctx = TestContext() ctx.is_pruned = False @@ -375,8 +375,8 @@ def test_get_tx_pool_statistics(self): # Can get the miner tx sum @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_get_miner_tx_sum(self) -> None: - sum = self._daemon.get_miner_tx_sum(0, min(5000, self._daemon.get_height())) - Utils.test_miner_tx_sum(sum) + tx_sum = self._daemon.get_miner_tx_sum(0, min(5000, self._daemon.get_height())) + Utils.test_miner_tx_sum(tx_sum) # Can get fee estimate @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") diff --git a/tests/test_monero_utils.py b/tests/test_monero_utils.py index cdad9f1..53299e4 100644 --- a/tests/test_monero_utils.py +++ b/tests/test_monero_utils.py @@ -103,7 +103,7 @@ def test_serialize_text_short(self, config: TestMoneroUtils.Config): binary: bytes = MoneroUtils.dict_to_binary(json_map) MoneroTestUtils.assert_true(len(binary) > 0) json_map2: dict[Any, Any] = MoneroUtils.binary_to_dict(binary) - + assert json_map == json_map2 # Can serialize json with long text diff --git a/tests/test_monero_wallet_common.py b/tests/test_monero_wallet_common.py index cab3c08..615a596 100644 --- a/tests/test_monero_wallet_common.py +++ b/tests/test_monero_wallet_common.py @@ -30,7 +30,7 @@ def parse(cls, parser: ConfigParser) -> BaseTestMoneroWallet.Config: config.seed = parser.get(section, "seed") return config - # region Private Methods + #region Private Methods @classmethod def _get_test_daemon(cls) -> MoneroDaemonRpc: @@ -67,19 +67,20 @@ def get_test_wallet(self) -> MoneroWallet: def is_random_wallet_config(cls, config: MoneroWalletConfig) -> bool: return config.seed is None and config.primary_address is None - # endregion + #endregion #region Fixtures @pytest.fixture(scope="class") - def config(self) -> BaseTestMoneroWallet.Config: + def test_config(self) -> BaseTestMoneroWallet.Config: parser = ConfigParser() - parser.read('config/test_monero_wallet_common.ini') + parser.read('tests/config/test_monero_wallet_common.ini') return BaseTestMoneroWallet.Config.parse(parser) #endregion - # region Tests + #region Tests + @pytest.mark.skipif(TestUtils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_create_wallet_random(self) -> None: """ @@ -981,4 +982,4 @@ def test_mining(self): wallet.start_mining(2, False, True) wallet.stop_mining() - # endregion + #endregion diff --git a/tests/test_monero_wallet_keys.py b/tests/test_monero_wallet_keys.py index 3ba6b25..53e907e 100644 --- a/tests/test_monero_wallet_keys.py +++ b/tests/test_monero_wallet_keys.py @@ -10,50 +10,13 @@ from test_monero_wallet_common import BaseTestMoneroWallet -@pytest.mark.skipif(True, reason="TODO") class TestMoneroWalletKeys(BaseTestMoneroWallet): _account_indices: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] _subaddress_indices: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] _wallet: MoneroWalletKeys = Utils.get_wallet_keys() # type: ignore - @classmethod - def _test_account(cls, account: Optional[MoneroAccount]): - assert account is not None - assert account.index is not None - assert account.index >= 0 - assert account.primary_address is not None - assert account.tag is None or len(account.tag) > 0 - - @classmethod - def _test_subaddress(cls, subaddress: Optional[MoneroSubaddress]): - assert subaddress is not None - assert subaddress.index is not None - assert subaddress.account_index is not None - assert subaddress.label is None or len(subaddress.label) > 0 - - def _get_subaddress(self, account_idx: int, subaddress_idx: int) -> Optional[MoneroSubaddress]: - subaddress_indices: list[int] = [subaddress_idx] - subaddresses = self._wallet.get_subaddresses(account_idx, subaddress_indices) - - if len(subaddresses) == 0: - return None - - return subaddresses[0] - - def _get_test_accounts(self, include_subaddresses: bool = False) -> list[MoneroAccount]: - account_indices = self._account_indices - subaddress_indices = self._subaddress_indices - accounts: list[MoneroAccount] = [] - for account_idx in account_indices: - account = self._wallet.get_account(account_idx) - - if include_subaddresses: - account.subaddresses = self._wallet.get_subaddresses(account_idx, subaddress_indices) - - accounts.append(account) - - return accounts + #region Overrides @classmethod @override @@ -110,6 +73,10 @@ def _get_seed_languages(self) -> list[str]: def get_test_wallet(self) -> MoneroWallet: return Utils.get_wallet_keys() + #endregion + + #region Disabled Tests + @pytest.mark.skip(reason="Wallet path not supported") @override def test_get_path(self) -> None: @@ -125,6 +92,108 @@ def test_set_daemon_connection(self): def test_sync_without_progress(self): return super().test_sync_without_progress() + @pytest.mark.skip(reason="Subaddress lookahead not supported") + @override + def test_subaddress_lookahead(self) -> None: + return super().test_subaddress_lookahead() + + @pytest.mark.skip(reason="Not implemented") + @override + def test_decode_integrated_address(self): + return super().test_decode_integrated_address() + + @pytest.mark.skip(reason="Get address index not supported") + @override + def test_get_address_indices(self): + return super().test_get_address_indices() + + @pytest.mark.skip(reason="Not supported") + @override + def test_wallet_equality_ground_truth(self): + return super().test_wallet_equality_ground_truth() + + @pytest.mark.skip(reason="Not supported") + @override + def test_get_height(self): + return super().test_get_height() + + @pytest.mark.skip(reason="Not supported") + @override + def test_get_height_by_date(self): + return super().test_get_height_by_date() + + @pytest.mark.skip(reason="Not supported") + @override + def test_get_all_balances(self): + return super().test_get_all_balances() + + @pytest.mark.skip(reason="Account creation not supported") + @override + def test_create_account_without_label(self): + return super().test_create_account_without_label() + + @pytest.mark.skip(reason="Account/label creation not supported") + @override + def test_create_account_with_label(self): + return super().test_create_account_with_label() + + @pytest.mark.skip(reason="Label creation not supported") + @override + def test_set_account_label(self): + return super().test_set_account_label() + + @pytest.mark.skip(reason="Not supported") + @override + def test_get_subaddresses_by_indices(self): + return super().test_get_subaddresses_by_indices() + + @pytest.mark.skip(reason="Subaddress creation not supported") + @override + def test_create_subaddress(self): + return super().test_create_subaddress() + + @pytest.mark.skip(reason="Labels not supported") + @override + def test_set_subaddress_label(self): + return super().test_set_subaddress_label() + + @pytest.mark.skip(reason="Tx note not supported") + @override + def test_set_tx_note(self) -> None: + return super().test_set_tx_note() + + @pytest.mark.skip(reason="Tx note not supported") + @override + def test_set_tx_notes(self) -> None: + return super().test_set_tx_notes() + + @pytest.mark.skip(reason="Export key images not supported") + @override + def test_export_key_images(self): + return super().test_export_key_images() + + @pytest.mark.skip(reason="Import key images not supported") + @override + def test_get_new_key_images_from_last_import(self): + return super().test_get_new_key_images_from_last_import() + + @pytest.mark.skip(reason="Import key images not supported") + @override + def test_import_key_images(self): + return super().test_import_key_images() + + @pytest.mark.skip(reason="Payment uri not supported") + @override + def test_get_payment_uri(self): + return super().test_get_payment_uri() + + @pytest.mark.skip(reason="Mining not supported") + @override + def test_mining(self): + return super().test_mining() + + #endregion + @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") @override def test_create_wallet_random(self) -> None: @@ -296,16 +365,6 @@ def test_create_wallet_from_keys(self) -> None: if e1 is not None: raise e1 - @pytest.mark.skip(reason="Subaddress lookahead not supported") - @override - def test_subaddress_lookahead(self) -> None: - return super().test_subaddress_lookahead() - - @pytest.mark.skip(reason="Not implemented") - @override - def test_decode_integrated_address(self): - return super().test_decode_integrated_address() - @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") @override def test_get_subaddress_address(self): @@ -333,54 +392,13 @@ def test_get_subaddress_address_out_of_range(self): Utils.assert_not_none(address) Utils.assert_true(len(address) > 0) - @pytest.mark.skip(reason="Get address index not supported") - @override - def test_get_address_indices(self): - return super().test_get_address_indices() - - @pytest.mark.skip(reason="Not supported") - @override - def test_wallet_equality_ground_truth(self): - return super().test_wallet_equality_ground_truth() - - @pytest.mark.skip(reason="Not supported") - @override - def test_get_height(self): - return super().test_get_height() - - @pytest.mark.skip(reason="Not supported") - @override - def test_get_height_by_date(self): - return super().test_get_height_by_date() - - @pytest.mark.skip(reason="Not supported") - @override - def test_get_all_balances(self): - return super().test_get_all_balances() - - @override - def test_get_accounts_without_subaddresses(self): - accounts = self._get_test_accounts() - assert len(accounts) > 0 - for account in accounts: - self._test_account(account) - assert len(account.subaddresses) == 0 - - @override - def test_get_accounts_with_subaddresses(self): - accounts = self._get_test_accounts(True) - assert len(accounts) > 0 - for account in accounts: - self._test_account(account) - assert len(account.subaddresses) > 0 - @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") @override def test_get_account(self): accounts = self._get_test_accounts() assert len(accounts) > 0 for account in accounts: - self._test_account(account) + Utils.test_account(account, False) # test without subaddresses assert account.index is not None @@ -391,20 +409,21 @@ def test_get_account(self): retrieved = self._wallet.get_account(account.index) retrieved.subaddresses = self._wallet.get_subaddresses(account.index, self._subaddress_indices) - @pytest.mark.skip(reason="Account creation not supported") - @override - def test_create_account_without_label(self): - return super().test_create_account_without_label() - - @pytest.mark.skip(reason="Account/label creation not supported") @override - def test_create_account_with_label(self): - return super().test_create_account_with_label() + def test_get_accounts_without_subaddresses(self): + accounts = self._get_test_accounts() + assert len(accounts) > 0 + for account in accounts: + Utils.test_account(account, False) + assert len(account.subaddresses) == 0 - @pytest.mark.skip(reason="Label creation not supported") @override - def test_set_account_label(self): - return super().test_set_account_label() + def test_get_accounts_with_subaddresses(self): + accounts = self._get_test_accounts(True) + assert len(accounts) > 0 + for account in accounts: + Utils.test_account(account, False) + assert len(account.subaddresses) > 0 @override def test_get_subaddresses(self): @@ -416,14 +435,9 @@ def test_get_subaddresses(self): subaddresses = wallet.get_subaddresses(account.index, self._subaddress_indices) assert len(subaddresses) > 0 for subaddress in subaddresses: - self._test_subaddress(subaddress) + Utils.test_subaddress(subaddress, False) assert account.index == subaddress.account_index - @pytest.mark.skip(reason="Not supported") - @override - def test_get_subaddresses_by_indices(self): - return super().test_get_subaddresses_by_indices() - @override def test_get_subaddress_by_index(self): accounts = self._get_test_accounts() @@ -435,54 +449,36 @@ def test_get_subaddress_by_index(self): for subaddress in subaddresses: assert subaddress.index is not None - self._test_subaddress(subaddress) + Utils.test_subaddress(subaddress, False) Utils.assert_subaddress_equal(subaddress, self._get_subaddress(account.index, subaddress.index)) # test plural call with single subaddr number Utils.assert_subaddress_equal( subaddress, self._wallet.get_subaddresses(account.index, [subaddress.index])[0] ) - @pytest.mark.skip(reason="Subaddress creation not supported") - @override - def test_create_subaddress(self): - return super().test_create_subaddress() + #region Utils - @pytest.mark.skip(reason="Labels not supported") - @override - def test_set_subaddress_label(self): - return super().test_set_subaddress_label() + def _get_subaddress(self, account_idx: int, subaddress_idx: int) -> Optional[MoneroSubaddress]: + subaddress_indices: list[int] = [subaddress_idx] + subaddresses = self._wallet.get_subaddresses(account_idx, subaddress_indices) - @pytest.mark.skip(reason="Tx note not supported") - @override - def test_set_tx_note(self) -> None: - return super().test_set_tx_note() + if len(subaddresses) == 0: + return None - @pytest.mark.skip(reason="Tx note not supported") - @override - def test_set_tx_notes(self) -> None: - return super().test_set_tx_notes() + return subaddresses[0] - @pytest.mark.skip(reason="Export key images not supported") - @override - def test_export_key_images(self): - return super().test_export_key_images() + def _get_test_accounts(self, include_subaddresses: bool = False) -> list[MoneroAccount]: + account_indices = self._account_indices + subaddress_indices = self._subaddress_indices + accounts: list[MoneroAccount] = [] + for account_idx in account_indices: + account = self._wallet.get_account(account_idx) - @pytest.mark.skip(reason="Import key images not supported") - @override - def test_get_new_key_images_from_last_import(self): - return super().test_get_new_key_images_from_last_import() + if include_subaddresses: + account.subaddresses = self._wallet.get_subaddresses(account_idx, subaddress_indices) - @pytest.mark.skip(reason="Import key images not supported") - @override - def test_import_key_images(self): - return super().test_import_key_images() + accounts.append(account) - @pytest.mark.skip(reason="Payment uri not supported") - @override - def test_get_payment_uri(self): - return super().test_get_payment_uri() + return accounts - @pytest.mark.skip(reason="Mining not supported") - @override - def test_mining(self): - return super().test_mining() + #endregion diff --git a/tests/utils/monero_test_utils.py b/tests/utils/monero_test_utils.py index 8feba43..f70eaff 100644 --- a/tests/utils/monero_test_utils.py +++ b/tests/utils/monero_test_utils.py @@ -560,7 +560,7 @@ def test_unsigned_big_integer(cls, value: Any, bool_val: bool = False): raise Exception("Value cannot be negative") @classmethod - def test_account(cls, account: Optional[MoneroAccount]): + def test_account(cls, account: Optional[MoneroAccount], full: bool = True): # test account assert account is not None assert account.index is not None @@ -568,52 +568,55 @@ def test_account(cls, account: Optional[MoneroAccount]): assert account.primary_address is not None MoneroUtils.validate_address(account.primary_address, cls.NETWORK_TYPE) - cls.test_unsigned_big_integer(account.balance) - cls.test_unsigned_big_integer(account.unlocked_balance) - - # if given, test subaddresses and that their balances add up to account balances - if len(account.subaddresses) > 0: - balance = 0 - unlocked_balance = 0 - i = 0 - j = len(account.subaddresses) - while i < j: - cls.test_subaddress(account.subaddresses[i]) - assert account.index == account.subaddresses[i].account_index - assert i == account.subaddresses[i].index - address_balance = account.subaddresses[i].balance - assert address_balance is not None - balance += address_balance - address_balance = account.subaddresses[i].unlocked_balance - assert address_balance is not None - unlocked_balance += address_balance - - assert account.balance == balance, "Subaddress balances " + str(balance) + " != account " + str(account.index) + " balance " + str(account.balance) - assert account.unlocked_balance == unlocked_balance, "Subaddress unlocked balances " + str(unlocked_balance) + " != account " + str(account.index) + " unlocked balance " + str(account.unlocked_balance) + if full: + cls.test_unsigned_big_integer(account.balance) + cls.test_unsigned_big_integer(account.unlocked_balance) + + # if given, test subaddresses and that their balances add up to account balances + if len(account.subaddresses) > 0: + balance = 0 + unlocked_balance = 0 + i = 0 + j = len(account.subaddresses) + while i < j: + cls.test_subaddress(account.subaddresses[i]) + assert account.index == account.subaddresses[i].account_index + assert i == account.subaddresses[i].index + address_balance = account.subaddresses[i].balance + assert address_balance is not None + balance += address_balance + address_balance = account.subaddresses[i].unlocked_balance + assert address_balance is not None + unlocked_balance += address_balance + msg1 = f"Subaddress balances {balance} != account {account.index} balance {account.balance}" + msg2 = f"Subaddress unlocked balances {unlocked_balance} != account {account.index} unlocked balance {account.unlocked_balance}" + assert account.balance == balance, msg1 + assert account.unlocked_balance == unlocked_balance, msg2 # tag must be undefined or non-empty tag = account.tag assert tag is None or len(tag) > 0 @classmethod - def test_subaddress(cls, subaddress: MoneroSubaddress): + def test_subaddress(cls, subaddress: MoneroSubaddress, full: bool = True): assert subaddress.account_index is not None assert subaddress.index is not None - assert subaddress.balance is not None - assert subaddress.num_unspent_outputs is not None - assert subaddress.num_blocks_to_unlock is not None + if full: + assert subaddress.balance is not None + assert subaddress.num_unspent_outputs is not None + assert subaddress.num_blocks_to_unlock is not None + cls.test_unsigned_big_integer(subaddress.balance) + cls.test_unsigned_big_integer(subaddress.unlocked_balance) + cls.assert_true(subaddress.num_unspent_outputs >= 0) + cls.assert_not_none(subaddress.is_used) + if subaddress.balance > 0: + cls.assert_true(subaddress.is_used) + cls.assert_true(subaddress.num_blocks_to_unlock >= 0) cls.assert_true(subaddress.account_index >= 0) cls.assert_true(subaddress.index >= 0) cls.assert_not_none(subaddress.address) cls.assert_true(subaddress.label is None or subaddress.label != "") - cls.test_unsigned_big_integer(subaddress.balance) - cls.test_unsigned_big_integer(subaddress.unlocked_balance) - cls.assert_true(subaddress.num_unspent_outputs >= 0) - cls.assert_not_none(subaddress.is_used) - if subaddress.balance > 0: - cls.assert_true(subaddress.is_used) - cls.assert_true(subaddress.num_blocks_to_unlock >= 0) @classmethod def assert_subaddress_equal(cls, subaddress: Optional[MoneroSubaddress], other: Optional[MoneroSubaddress]): @@ -810,10 +813,10 @@ def test_ban(cls, ban: Optional[MoneroBan]) -> None: assert ban.seconds is not None @classmethod - def test_miner_tx_sum(cls, sum: Optional[MoneroMinerTxSum]) -> None: - assert sum is not None - cls.test_unsigned_big_integer(sum.emission_sum, True) - cls.test_unsigned_big_integer(sum.fee_sum, True) + def test_miner_tx_sum(cls, tx_sum: Optional[MoneroMinerTxSum]) -> None: + assert tx_sum is not None + cls.test_unsigned_big_integer(tx_sum.emission_sum, True) + cls.test_unsigned_big_integer(tx_sum.fee_sum, True) @classmethod def get_unrelayed_tx(cls, wallet: MoneroWallet, account_idx: int):