From f09db7df920e86d4cec7893dfe3775c504ddf886 Mon Sep 17 00:00:00 2001 From: TTW <35064951+ttw225@users.noreply.github.com> Date: Mon, 25 Mar 2019 14:14:05 +0800 Subject: [PATCH 1/8] Update addresses.rst Using AddressGenerator's argument was wrong. Using `start` instead `index`! --- docs/addresses.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/addresses.rst b/docs/addresses.rst index 8669c54..c3403ae 100644 --- a/docs/addresses.rst +++ b/docs/addresses.rst @@ -80,13 +80,13 @@ Using AddressGenerator generator = AddressGenerator(b'SEED9GOES9HERE') # Generate a list of addresses: - addresses = generator.get_addresses(index=0, count=5) + addresses = generator.get_addresses(start=0, count=5) # Generate a list of addresses in reverse order: - addresses = generator.get_addresses(index=42, count=10, step=-1) + addresses = generator.get_addresses(start=42, count=10, step=-1) # Create an iterator, advancing 5 indices each iteration. - iterator = generator.create_iterator(index=86, step=5) + iterator = generator.create_iterator(start=86, step=5) for address in iterator: ... From a6d12a11da70b9bc13f3a6949fedc0a695afa90b Mon Sep 17 00:00:00 2001 From: Philipp de Col Date: Fri, 13 Sep 2019 16:05:10 +0200 Subject: [PATCH 2/8] [#232] Deprecate detox --- README.rst | 9 ++++----- setup.py | 3 ++- tox.ini | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 2cb91ec..59915f2 100644 --- a/README.rst +++ b/README.rst @@ -61,14 +61,14 @@ To run unit tests after installing from source:: PyOTA is also compatible with `tox`_, which will run the unit tests in different virtual environments (one for each supported version of Python). -To run the unit tests, it is recommended that you use the `detox`_ library. -detox speeds up the tests by running them in parallel. +To run the unit tests, it is recommended that you use the ``-p`` argument. +This speeds up the tests by running them in parallel. Install PyOTA with the ``test-runner`` extra to set up the necessary -dependencies, and then you can run the tests with the ``detox`` command:: +dependencies, and then you can run the tests with the ``tox`` command:: pip install -e .[test-runner] - detox -v + tox -v -p all ============= Documentation @@ -102,6 +102,5 @@ can also build the documentation locally: .. _PyOTA Bug Tracker: https://github.com/iotaledger/iota.lib.py/issues .. _ReadTheDocs: https://pyota.readthedocs.io/ .. _dedicated forum: https://forum.iota.org/ -.. _detox: https://pypi.python.org/pypi/detox .. _official API: https://iota.readme.io/ .. _tox: https://tox.readthedocs.io/ diff --git a/setup.py b/setup.py index 4a0aac6..cf2d869 100644 --- a/setup.py +++ b/setup.py @@ -76,7 +76,8 @@ extras_require={ 'ccurl': ['pyota-ccurl'], 'docs-builder': ['sphinx', 'sphinx_rtd_theme'], - 'test-runner': ['detox'] + tests_require, + # tox is able to run the tests in parallel since version 3.7 + 'test-runner': ['tox >= 3.7'] + tests_require, }, test_suite='test', diff --git a/tox.ini b/tox.ini index 080f499..3220f26 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,4 @@ -# Tox (http://tox.testrun.org/) is a tool for running tests +# Tox (https://tox.readthedocs.io/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. From ec627b1beae972016a843a8e3dd5276f8365a303 Mon Sep 17 00:00:00 2001 From: Philipp de Col Date: Fri, 13 Sep 2019 16:35:00 +0200 Subject: [PATCH 3/8] Fix broken links in README.rst --- README.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 59915f2..e7871d6 100644 --- a/README.rst +++ b/README.rst @@ -18,8 +18,6 @@ If you want to get involved in the community, need help with getting setup, have any issues related with the library or just want to discuss Blockchain, Distributed Ledgers and IoT with other people, feel free to join our `Discord`_. -You can also ask questions on our `dedicated forum`_. - If you encounter any issues while using PyOTA, please report them using the `PyOTA Bug Tracker`_. @@ -98,9 +96,8 @@ can also build the documentation locally: make html .. _Create virtualenv: https://realpython.com/blog/python/python-virtual-environments-a-primer/ -.. _Discord: https://discord.gg/7Gu2mG5 +.. _Discord: https://discord.iota.org/ .. _PyOTA Bug Tracker: https://github.com/iotaledger/iota.lib.py/issues .. _ReadTheDocs: https://pyota.readthedocs.io/ -.. _dedicated forum: https://forum.iota.org/ -.. _official API: https://iota.readme.io/ +.. _official API: https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference .. _tox: https://tox.readthedocs.io/ From e2b90a9e2f3964d4898a6816f2c09adc1ad74458 Mon Sep 17 00:00:00 2001 From: Philipp de Col Date: Sun, 15 Sep 2019 17:10:54 +0200 Subject: [PATCH 4/8] [#99] Implement find transaction objects command --- README.rst | 4 +- docs/api.rst | 34 +++++ iota/api.py | 44 +++++++ iota/commands/extended/__init__.py | 1 + .../extended/find_transaction_objects.py | 55 ++++++++ iota/commands/extended/is_reattachable.py | 9 +- iota/commands/extended/utils.py | 27 +--- .../extended/find_transaction_objects.py | 118 ++++++++++++++++++ 8 files changed, 261 insertions(+), 31 deletions(-) create mode 100644 iota/commands/extended/find_transaction_objects.py create mode 100644 test/commands/extended/find_transaction_objects.py diff --git a/README.rst b/README.rst index 2cb91ec..6cc9321 100644 --- a/README.rst +++ b/README.rst @@ -80,14 +80,14 @@ can also build the documentation locally: #. Install extra dependencies (you only have to do this once):: - pip install '.[docs-builder]' + pip install .[docs-builder] .. tip:: To install the CCurl extension and the documentation builder tools together, use the following command:: - pip install '.[ccurl,docs-builder]' + pip install .[ccurl,docs-builder] #. Switch to the ``docs`` directory:: diff --git a/docs/api.rst b/docs/api.rst index 1a9c176..778db6a 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -36,6 +36,40 @@ This method returns a ``dict`` with the following items: broadcast/stored. Should be the same as the value of the ``trytes`` parameter. + +``find_transaction_objects`` +---------------------------- + +A more extensive version of the core API ``find_transactions`` that returns +transaction objects instead of hashes. + +Effectively, this is ``find_transactions`` + ``get_trytes`` + converting +the trytes into transaction objects. It accepts the same parameters +as ``find_transactions`` + +Find the transactions which match the specified input. +All input values are lists, for which a list of return values +(transaction hashes), in the same order, is returned for all +individual elements. Using multiple of these input fields returns the +intersection of the values. + +Parameters +~~~~~~~~~~ + +- ``bundles: Optional[Iterable[BundleHash]]``: List of bundle IDs. +- ``addresses: Optional[Iterable[Address]]``: List of addresses. +- ``tags: Optional[Iterable[Tag]]``: List of tags. +- ``param: Optional[Iterable[TransactionHash]]``: List of approvee + transaction IDs. + +Return +~~~~~~ + +This method returns a ``dict`` with the following items: + +- ``transactions: List[Transaction]``: List of Transaction objects that + match the input + ``get_account_data`` -------------------- diff --git a/iota/api.py b/iota/api.py index 761f50f..7a31609 100644 --- a/iota/api.py +++ b/iota/api.py @@ -518,6 +518,50 @@ def broadcast_and_store(self, trytes): """ return extended.BroadcastAndStoreCommand(self.adapter)(trytes=trytes) + def find_transaction_objects( + self, + bundles=None, # type: Optional[Iterable[BundleHash]] + addresses=None, # type: Optional[Iterable[Address]] + tags=None, # type: Optional[Iterable[Tag]] + approvees=None, # type: Optional[Iterable[TransactionHash]] + ): + # type: (...) -> dict + """ + A more extensive version of :py:meth:`find_transactions` that + returns transaction objects instead of hashes. + + Effectively, this is ``find_transactions`` + ``get_trytes`` + + converting the trytes into transaction objects. + + It accepts the same parameters as :py:meth:`find_transactions` + + :param bundles: + List of bundle IDs. + + :param addresses: + List of addresses. + + :param tags: + List of tags. + + :param approvees: + List of approvee transaction IDs. + + :return: + Dict with the following structure:: + + { + 'transactions': List[Transaction], + List of Transaction objects that match the input. + } + """ + return extended.FindTransactionObjectsCommand(self.adapter)( + bundles=bundles, + addresses=addresses, + tags=tags, + approvees=approvees, + ) + def get_account_data(self, start=0, stop=None, inclusion_states=False, security_level=None): # type: (int, Optional[int], bool, Optional[int]) -> dict """ diff --git a/iota/commands/extended/__init__.py b/iota/commands/extended/__init__.py index 71d766f..4a267f2 100644 --- a/iota/commands/extended/__init__.py +++ b/iota/commands/extended/__init__.py @@ -12,6 +12,7 @@ unicode_literals from .broadcast_and_store import * +from .find_transaction_objects import * from .get_account_data import * from .get_bundles import * from .get_inputs import * diff --git a/iota/commands/extended/find_transaction_objects.py b/iota/commands/extended/find_transaction_objects.py new file mode 100644 index 0000000..e0dd096 --- /dev/null +++ b/iota/commands/extended/find_transaction_objects.py @@ -0,0 +1,55 @@ +# coding=utf-8 +from __future__ import absolute_import, division, print_function, \ + unicode_literals + +from typing import Iterable, List, Optional + +from iota import Address, BundleHash, Tag, Transaction, TransactionHash +from iota.commands.core import GetTrytesCommand, FindTransactionsCommand + +__all__ = [ + 'FindTransactionObjectsCommand', +] + + +class FindTransactionObjectsCommand(FindTransactionsCommand): + """ + Executes `FindTransactionObjects` command. + + See :py:meth:`iota.api.StrictIota.find_transaction_objects`. + """ + command = 'findTransactionObjects' + + def get_response_filter(self): + pass + + def _execute(self, request): + bundles = request\ + .get('bundles') # type: Optional[Iterable[BundleHash]] + addresses = request\ + .get('addresses') # type: Optional[Iterable[Address]] + tags = request\ + .get('tags') # type: Optional[Iterable[Tag]] + approvees = request\ + .get('approvees') # type: Optional[Iterable[TransactionHash]] + + ft_response = FindTransactionsCommand(adapter=self.adapter)( + bundles=bundles, + addresses=addresses, + tags=tags, + approvees=approvees, + ) + + hashes = ft_response['hashes'] + transactions = [] + if hashes: + gt_response = GetTrytesCommand(adapter=self.adapter)(hashes=hashes) + + transactions = list(map( + Transaction.from_tryte_string, + gt_response.get('trytes') or [], + )) # type: List[Transaction] + + return { + 'transactions': transactions, + } diff --git a/iota/commands/extended/is_reattachable.py b/iota/commands/extended/is_reattachable.py index 8238dc6..f136a19 100644 --- a/iota/commands/extended/is_reattachable.py +++ b/iota/commands/extended/is_reattachable.py @@ -8,8 +8,8 @@ from iota import Address from iota.commands import FilterCommand, RequestFilter, ResponseFilter -from iota.commands.extended import GetLatestInclusionCommand -from iota.commands.extended.utils import find_transaction_objects +from iota.commands.extended import FindTransactionObjectsCommand, \ + GetLatestInclusionCommand from iota.filters import Trytes __all__ = [ @@ -33,10 +33,9 @@ def _execute(self, request): addresses = request['addresses'] # type: List[Address] # fetch full transaction objects - transactions = find_transaction_objects( - adapter=self.adapter, + transactions = FindTransactionObjectsCommand(adapter=self.adapter)( addresses=addresses, - ) + )['transactions'] # Map and filter transactions which have zero value. # If multiple transactions for the same address are returned, diff --git a/iota/commands/extended/utils.py b/iota/commands/extended/utils.py index 740ec43..a8d3eb8 100644 --- a/iota/commands/extended/utils.py +++ b/iota/commands/extended/utils.py @@ -9,6 +9,7 @@ from iota.adapter import BaseAdapter from iota.commands.core.find_transactions import FindTransactionsCommand from iota.commands.core.get_trytes import GetTrytesCommand +from iota.commands.extended import FindTransactionObjectsCommand from iota.commands.extended.get_bundles import GetBundlesCommand from iota.commands.extended.get_latest_inclusion import \ GetLatestInclusionCommand @@ -16,27 +17,6 @@ from iota.crypto.types import Seed -def find_transaction_objects(adapter, **kwargs): - # type: (BaseAdapter, **Iterable) -> List[Transaction] - """ - Finds transactions matching the specified criteria, fetches the - corresponding trytes and converts them into Transaction objects. - """ - ft_response = FindTransactionsCommand(adapter)(**kwargs) - - hashes = ft_response['hashes'] - - if hashes: - gt_response = GetTrytesCommand(adapter)(hashes=hashes) - - return list(map( - Transaction.from_tryte_string, - gt_response.get('trytes') or [], - )) # type: List[Transaction] - - return [] - - def iter_used_addresses( adapter, # type: BaseAdapter seed, # type: Seed @@ -103,10 +83,9 @@ def get_bundles_from_transaction_hashes( non_tail_bundle_hashes.add(txn.bundle_hash) if non_tail_bundle_hashes: - for txn in find_transaction_objects( - adapter=adapter, + for txn in FindTransactionObjectsCommand(adapter=adapter)( bundles=list(non_tail_bundle_hashes), - ): + )['transactions']: if txn.is_tail: if txn.hash not in tail_transaction_hashes: all_transactions.append(txn) diff --git a/test/commands/extended/find_transaction_objects.py b/test/commands/extended/find_transaction_objects.py new file mode 100644 index 0000000..a98b97c --- /dev/null +++ b/test/commands/extended/find_transaction_objects.py @@ -0,0 +1,118 @@ +# coding=utf-8 +from __future__ import absolute_import, division, print_function, \ + unicode_literals + +from unittest import TestCase + +import mock + +from iota import Iota, MockAdapter, Transaction +from iota.commands.extended import FindTransactionObjectsCommand + + +class FindTransactionObjectsCommandTestCase(TestCase): + # noinspection SpellCheckingInspection + def setUp(self): + super(FindTransactionObjectsCommandTestCase, self).setUp() + + self.adapter = MockAdapter() + self.command = FindTransactionObjectsCommand(self.adapter) + + # Define values that we can reuse across tests. + self.address = 'A' * 81 + self.transaction_hash = \ + b'BROTOVRCAEMFLRWGPVWDPDTBRAMLHVCHQDEHXLCWH' \ + b'KKXLVDFCPIJEUZTPPFMPQQ9KOHAEUAMMVJN99999' + self.trytes = \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999999999999999999999999999999999999999999999999' \ + b'99999999999999999AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' \ + b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA99999999999999999999999999' \ + b'9QC9999999999999999999999999PQYJHAD99999999999999999999WHIUDFV' \ + b'IFXNBJVEHYPLDADIDINGAWMHYIJNPYUDWXCAWL9GSKTUIZLJGGFIXEIYTJEDQZ' \ + b'TIYRXHC9PBWBDSOTEJTQTYYSZLVTFLDQMZSGLHKLYVJOLMXIJJRTGS9RYBXLAT' \ + b'ZJXBVBCPUGWRUKZJYLBGPKRKWIA9999FPYHMFFWMMKOHTSAPMMATZQLWXJSPMT' \ + b'JSRQIPMDCQXFFMXMHCYDKVJCFSRECAVALCOFIYCJLNRZZZ9999999999999999' \ + b'999999999999999KITCXNZOF999999999MMMMMMMMMEA9999F9999999999999' \ + b'9999999' + + def test_wireup(self): + """ + Verify that the command is wired up correctly. + """ + self.assertIsInstance( + Iota(self.adapter).findTransactionObjects, + FindTransactionObjectsCommand, + ) + + def test_transaction_found(self): + """ + A transaction is found with the inputs. A transaction object is + returned + """ + with mock.patch( + 'iota.commands.core.find_transactions.FindTransactionsCommand.' + '_execute', + mock.Mock(return_value={'hashes': [self.transaction_hash, ]}), + ): + with mock.patch( + 'iota.commands.core.get_trytes.GetTrytesCommand._execute', + mock.Mock(return_value={'trytes': [self.trytes, ]}), + ): + response = self.command(addresses=[self.address]) + + self.assertEqual(len(response['transactions']), 1) + transaction = response['transactions'][0] + self.assertIsInstance(transaction, Transaction) + self.assertEqual(transaction.address, self.address) + + def test_no_transactions_fround(self): + """ + No transaction is found with the inputs. An empty list is returned + """ + with mock.patch( + 'iota.commands.core.find_transactions.FindTransactionsCommand.' + '_execute', + mock.Mock(return_value={'hashes': []}), + ): + response = self.command(addresses=[self.address]) + + self.assertDictEqual( + response, + { + 'transactions': [], + }, + ) From dbf0de92e43f9c48404b511f06067415c1688c6b Mon Sep 17 00:00:00 2001 From: Philipp de Col Date: Sat, 21 Sep 2019 12:14:04 +0200 Subject: [PATCH 5/8] [#235] Update the core commands to match the latest IRI 1.8.1 release --- docs/api.rst | 3 +- docs/getting_started.rst | 4 +- iota/api.py | 94 ++- iota/commands/core/__init__.py | 4 +- iota/commands/core/check_consistency.py | 11 +- iota/commands/core/get_balances.py | 40 +- .../commands/core/get_missing_transactions.py | 47 ++ .../core/get_node_api_configuration.py | 45 ++ .../core/were_addresses_spent_from.py | 15 +- test/commands/core/check_consistency_test.py | 497 +++++++------- test/commands/core/get_balances_test.py | 617 ++++++++++-------- .../core/get_missing_transactions_test.py | 116 ++++ .../core/get_node_api_configuration_test.py | 95 +++ .../core/get_transactions_to_approve_test.py | 428 ++++++------ .../core/were_addresses_spent_from_test.py | 357 +++++----- 15 files changed, 1454 insertions(+), 919 deletions(-) create mode 100644 iota/commands/core/get_missing_transactions.py create mode 100644 iota/commands/core/get_node_api_configuration.py create mode 100644 test/commands/core/get_missing_transactions_test.py create mode 100644 test/commands/core/get_node_api_configuration_test.py diff --git a/docs/api.rst b/docs/api.rst index 778db6a..d6eceac 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -9,7 +9,8 @@ These methods are "low level" and generally do not need to be called directly. For the full documentation of all the Core API calls, please refer -to the `official documentation `__. +to the `official documentation `__. Extended API ============ diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 6da3a62..da254bc 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -1,6 +1,6 @@ Installation ============ -PyOTA is compatible with Python 3.6, 3.5 and 2.7. +PyOTA is compatible with Python 3.7, 3.6, 3.5 and 2.7. Install PyOTA using `pip`: @@ -76,7 +76,7 @@ your API requests so that they contain the necessary authentication metadata. ) .. _forum: https://forum.iota.org/ -.. _official api: https://iota.readme.io/ +.. _official api: https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference .. _pyota-ccurl extension: https://pypi.python.org/pypi/PyOTA-CCurl .. _run your own node.: http://iotasupport.com/headlessnode.shtml .. _slack: http://slack.iota.org/ diff --git a/iota/api.py b/iota/api.py index 7a31609..83aebb5 100644 --- a/iota/api.py +++ b/iota/api.py @@ -63,7 +63,7 @@ class StrictIota(object): References: - - https://iota.readme.io/docs/getting-started + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference """ commands = discover_commands('iota.commands.core') @@ -100,7 +100,7 @@ def __getattr__(self, command): References: - - https://iota.readme.io/docs/making-requests + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference """ # Fix an error when invoking :py:func:`help`. # https://github.com/iotaledger/iota.lib.py/issues/41 @@ -165,7 +165,7 @@ def add_neighbors(self, uris): References: - - https://iota.readme.io/docs/addneighors + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#addneighbors """ return core.AddNeighborsCommand(self.adapter)(uris=uris) @@ -190,7 +190,7 @@ def attach_to_tangle( References: - - https://iota.readme.io/docs/attachtotangle + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#attachtotangle """ if min_weight_magnitude is None: min_weight_magnitude = self.default_min_weight_magnitude @@ -212,7 +212,7 @@ def broadcast_transactions(self, trytes): References: - - https://iota.readme.io/docs/broadcasttransactions + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#broadcasttransactions """ return core.BroadcastTransactionsCommand(self.adapter)(trytes=trytes) @@ -242,6 +242,10 @@ def check_consistency(self, tails): This field will only exist set if ``state`` is ``False``. } + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#checkconsistency """ return core.CheckConsistencyCommand(self.adapter)( tails=tails, @@ -280,7 +284,7 @@ def find_transactions( References: - - https://iota.readme.io/docs/findtransactions + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#findtransactions """ return core.FindTransactionsCommand(self.adapter)( bundles=bundles, @@ -289,8 +293,13 @@ def find_transactions( approvees=approvees, ) - def get_balances(self, addresses, threshold=100): - # type: (Iterable[Address], int) -> dict + def get_balances( + self, + addresses, # type: Iterable[Address] + threshold=100, # type: int + tips=None, # type: Optional[Iterable[TransactionHash]] + ): + # type: (...) -> dict """ Similar to :py:meth:`get_inclusion_states`. Returns the confirmed balance which a list of addresses have at the latest @@ -305,15 +314,19 @@ def get_balances(self, addresses, threshold=100): List of addresses to get the confirmed balance for. :param threshold: - Confirmation threshold. + Confirmation threshold between 0 and 100. + + :param tips: + Tips whose history of transactions to traverse to find the balance. References: - - https://iota.readme.io/docs/getbalances + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getbalances """ return core.GetBalancesCommand(self.adapter)( addresses=addresses, threshold=threshold, + tips=tips, ) def get_inclusion_states(self, transactions, tips): @@ -334,13 +347,25 @@ def get_inclusion_states(self, transactions, tips): References: - - https://iota.readme.io/docs/getinclusionstates + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getinclusionstates """ return core.GetInclusionStatesCommand(self.adapter)( transactions=transactions, tips=tips, ) + def get_missing_transactions(self): + # type: () -> dict + """ + Returns all transaction hashes that a node is currently requesting + from its neighbors. + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getmissingtransactions + """ + return core.GetMissingTransactionsCommand(self.adapter)() + def get_neighbors(self): # type: () -> dict """ @@ -351,10 +376,21 @@ def get_neighbors(self): References: - - https://iota.readme.io/docs/getneighborsactivity + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getneighbors """ return core.GetNeighborsCommand(self.adapter)() + def get_node_api_configuration(self): + # type: () -> dict + """ + Returns a node's API configuration settings. + + References: + + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getnodeapiconfiguration + """ + return core.GetNodeAPIConfigurationCommand(self.adapter)() + def get_node_info(self): # type: () -> dict """ @@ -362,7 +398,7 @@ def get_node_info(self): References: - - https://iota.readme.io/docs/getnodeinfo + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#getnodeinfo """ return core.GetNodeInfoCommand(self.adapter)() @@ -374,13 +410,13 @@ def get_tips(self): References: - - https://iota.readme.io/docs/gettips - - https://iota.readme.io/docs/glossary#iota-terms + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#gettips + - https://docs.iota.org/docs/dev-essentials/0.1/references/glossary """ return core.GetTipsCommand(self.adapter)() - def get_transactions_to_approve(self, depth): - # type: (int) -> dict + def get_transactions_to_approve(self, depth, reference=None): + # type: (int, Optional[TransactionHash]) -> dict """ Tip selection which returns ``trunkTransaction`` and ``branchTransaction``. @@ -393,11 +429,19 @@ def get_transactions_to_approve(self, depth): will perform for the network (as it will confirm more transactions that way). + :param reference: + Transaction hash from which to start the weighted random walk. + Use this parameter to make sure the returned tip transaction hashes + approve a given reference transaction. + References: - - https://iota.readme.io/docs/gettransactionstoapprove + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#gettransactionstoapprove """ - return core.GetTransactionsToApproveCommand(self.adapter)(depth=depth) + return core.GetTransactionsToApproveCommand(self.adapter)( + depth=depth, + reference=reference, + ) def get_trytes(self, hashes): # type: (Iterable[TransactionHash]) -> dict @@ -407,7 +451,7 @@ def get_trytes(self, hashes): References: - - https://iota.readme.io/docs/gettrytes + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#gettrytes """ return core.GetTrytesCommand(self.adapter)(hashes=hashes) @@ -419,7 +463,7 @@ def interrupt_attaching_to_tangle(self): References: - - https://iota.readme.io/docs/interruptattachingtotangle + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#interruptattachingtotangle """ return core.InterruptAttachingToTangleCommand(self.adapter)() @@ -435,7 +479,7 @@ def remove_neighbors(self, uris): References: - - https://iota.readme.io/docs/removeneighors + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#removeneighbors """ return core.RemoveNeighborsCommand(self.adapter)(uris=uris) @@ -449,7 +493,7 @@ def store_transactions(self, trytes): References: - - https://iota.readme.io/docs/storetransactions + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#storetransactions """ return core.StoreTransactionsCommand(self.adapter)(trytes=trytes) @@ -464,7 +508,7 @@ def were_addresses_spent_from(self, addresses): References: - - https://iota.readme.io/docs/wereaddressesspentfrom + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#wereaddressesspentfrom """ return core.WereAddressesSpentFromCommand(self.adapter)( addresses=addresses, @@ -478,7 +522,7 @@ class Iota(StrictIota): References: - - https://iota.readme.io/docs/getting-started + - https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference - https://github.com/iotaledger/wiki/blob/master/api-proposal.md """ commands = discover_commands('iota.commands.extended') diff --git a/iota/commands/core/__init__.py b/iota/commands/core/__init__.py index 81f120a..64a9874 100644 --- a/iota/commands/core/__init__.py +++ b/iota/commands/core/__init__.py @@ -4,7 +4,7 @@ References: -- https://iota.readme.io/docs/getting-started +- https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference """ from __future__ import absolute_import, division, print_function, \ @@ -17,7 +17,9 @@ from .find_transactions import * from .get_balances import * from .get_inclusion_states import * +from .get_missing_transactions import * from .get_neighbors import * +from .get_node_api_configuration import * from .get_node_info import * from .get_tips import * from .get_transactions_to_approve import * diff --git a/iota/commands/core/check_consistency.py b/iota/commands/core/check_consistency.py index 598322e..054a2b3 100644 --- a/iota/commands/core/check_consistency.py +++ b/iota/commands/core/check_consistency.py @@ -5,7 +5,7 @@ import filters as f from iota import TransactionHash -from iota.commands import FilterCommand, RequestFilter +from iota.commands import FilterCommand, RequestFilter, ResponseFilter from iota.filters import Trytes __all__ = [ @@ -25,7 +25,7 @@ def get_request_filter(self): return CheckConsistencyRequestFilter() def get_response_filter(self): - pass + return CheckConsistencyResponseFilter() class CheckConsistencyRequestFilter(RequestFilter): @@ -36,3 +36,10 @@ def __init__(self): f.Array | f.FilterRepeater(f.Required | Trytes(TransactionHash)), }) + + +class CheckConsistencyResponseFilter(ResponseFilter): + def __init__(self): + super(CheckConsistencyResponseFilter, self).__init__({ + 'state': f.Required | f.Type(bool), + }) diff --git a/iota/commands/core/get_balances.py b/iota/commands/core/get_balances.py index 423e9ca..00c8c93 100644 --- a/iota/commands/core/get_balances.py +++ b/iota/commands/core/get_balances.py @@ -3,8 +3,9 @@ unicode_literals import filters as f +from six import iteritems -from iota import Address +from iota import TransactionHash from iota.commands import FilterCommand, RequestFilter, ResponseFilter from iota.filters import AddressNoChecksum, Trytes @@ -44,19 +45,50 @@ def __init__(self): f.Min(0) | f.Max(100) | f.Optional(default=100), + + 'tips': + f.Array | f.FilterRepeater( + f.Required | + Trytes(TransactionHash) | + f.Unicode(encoding='ascii', normalize=False), + ) }, allow_missing_keys={ - 'threshold', + 'threshold', 'tips', }, ) + def _apply(self, value): + value = super(GetBalancesRequestFilter, self)._apply( + value + ) # type: dict + + if self._has_errors: + return value + + # Remove null search terms. + # Note: We will assume that empty lists are intentional. + search_terms = { + term: query + for term, query in iteritems(value) + if query is not None + } + + return search_terms + class GetBalancesResponseFilter(ResponseFilter): def __init__(self): super(GetBalancesResponseFilter, self).__init__({ 'balances': f.Array | f.FilterRepeater(f.Int), - 'milestone': - f.ByteString(encoding='ascii') | Trytes(Address), + 'milestoneIndex': f.Int, + + 'references': + f.Array | f.FilterRepeater( + f.Required | + Trytes(TransactionHash) | + f.Unicode(encoding='ascii', normalize=False), + ), }) diff --git a/iota/commands/core/get_missing_transactions.py b/iota/commands/core/get_missing_transactions.py new file mode 100644 index 0000000..c0d3ee4 --- /dev/null +++ b/iota/commands/core/get_missing_transactions.py @@ -0,0 +1,47 @@ +# coding=utf-8 +from __future__ import absolute_import, division, print_function, \ + unicode_literals + +import filters as f + +from iota import TransactionHash +from iota.commands import FilterCommand, RequestFilter, ResponseFilter +from iota.filters import Trytes + +__all__ = [ + 'GetMissingTransactionsCommand', +] + + +class GetMissingTransactionsCommand(FilterCommand): + """ + Executes `getMissingTransactions` command. + + See :py:meth:`iota.api.StrictIota.get_missing_transactions`. + """ + command = 'getMissingTransactions' + + def get_request_filter(self): + return GetMissingTransactionsRequestFilter() + + def get_response_filter(self): + return GetMissingTransactionsResponseFilter() + + +class GetMissingTransactionsRequestFilter(RequestFilter): + def __init__(self): + # ``getMissingTransactions`` does not accept any parameters. + # Using a filter here just to enforce that the request is empty. + super(GetMissingTransactionsRequestFilter, self).__init__({}) + + +class GetMissingTransactionsResponseFilter(ResponseFilter): + def __init__(self): + super(GetMissingTransactionsResponseFilter, self).__init__({ + 'hashes': + f.FilterRepeater( + f.ByteString(encoding='ascii') | + Trytes(TransactionHash) + ) | + f.Optional(default=[]), + }) diff --git a/iota/commands/core/get_node_api_configuration.py b/iota/commands/core/get_node_api_configuration.py new file mode 100644 index 0000000..60475fa --- /dev/null +++ b/iota/commands/core/get_node_api_configuration.py @@ -0,0 +1,45 @@ +# coding=utf-8 +from __future__ import absolute_import, division, print_function, \ + unicode_literals + +import filters as f + +from iota.commands import FilterCommand, RequestFilter, ResponseFilter + +__all__ = [ + 'GetNodeAPIConfigurationCommand', +] + + +class GetNodeAPIConfigurationCommand(FilterCommand): + """ + Executes `getNodeAPIConfiguration` command. + + See :py:meth:`iota.api.StrictIota.get_node_api_configuration`. + """ + command = 'getNodeAPIConfiguration' + + def get_request_filter(self): + return GetNodeAPIConfigurationRequestFilter() + + def get_response_filter(self): + return GetNodeAPIConfigurationResponseFilter() + + +class GetNodeAPIConfigurationRequestFilter(RequestFilter): + def __init__(self): + # ``getNodeAPIConfiguration`` does not accept any parameters. + # Using a filter here just to enforce that the request is empty. + super(GetNodeAPIConfigurationRequestFilter, self).__init__({}) + + +class GetNodeAPIConfigurationResponseFilter(ResponseFilter): + def __init__(self): + super(GetNodeAPIConfigurationResponseFilter, self).__init__({ + 'maxFindTransactions': f.Type(int), + 'maxRequestsList': f.Type(int), + 'maxGetTrytes': f.Type(int), + 'maxBodyLength': f.Type(int), + 'testNet': f.Type(bool), + 'milestoneStartIndex': f.Type(int), + }) diff --git a/iota/commands/core/were_addresses_spent_from.py b/iota/commands/core/were_addresses_spent_from.py index 0d41e17..594e6c5 100644 --- a/iota/commands/core/were_addresses_spent_from.py +++ b/iota/commands/core/were_addresses_spent_from.py @@ -4,7 +4,7 @@ import filters as f -from iota.commands import FilterCommand, RequestFilter +from iota.commands import FilterCommand, RequestFilter, ResponseFilter from iota.filters import AddressNoChecksum __all__ = [ @@ -24,7 +24,7 @@ def get_request_filter(self): return WereAddressesSpentFromRequestFilter() def get_response_filter(self): - pass + return WereAddressesSpentFromResponseFilter() class WereAddressesSpentFromRequestFilter(RequestFilter): @@ -37,3 +37,14 @@ def __init__(self): f.Unicode(encoding='ascii', normalize=False), ), }) + + +class WereAddressesSpentFromResponseFilter(ResponseFilter): + def __init__(self): + super(WereAddressesSpentFromResponseFilter, self).__init__({ + 'states': + f.Array | f.FilterRepeater( + f.Required | + f.Type(bool), + ), + }) diff --git a/test/commands/core/check_consistency_test.py b/test/commands/core/check_consistency_test.py index 3b684b5..e994ccd 100644 --- a/test/commands/core/check_consistency_test.py +++ b/test/commands/core/check_consistency_test.py @@ -1,6 +1,6 @@ # coding=utf-8 from __future__ import absolute_import, division, print_function, \ - unicode_literals + unicode_literals from unittest import TestCase @@ -14,234 +14,273 @@ class CheckConsistencyRequestFilterTestCase(BaseFilterTestCase): - filter_type = CheckConsistencyCommand(MockAdapter()).get_request_filter - skip_value_check = True - - # noinspection SpellCheckingInspection - def setUp(self): - super(CheckConsistencyRequestFilterTestCase, self).setUp() - - self.hash1 = ( - 'TESTVALUE9DONTUSEINPRODUCTION99999DXSCAD' - 'YBVDCTTBLHFYQATFZPYPCBG9FOUKIGMYIGLHM9NEZ' - ) - - self.hash2 = ( - 'TESTVALUE9DONTUSEINPRODUCTION99999EMFYSM' - 'HWODIAPUTTFDLQRLYIDAUIPJXXEXZZSBVKZEBWGAN' - ) - - def test_pass_happy_path(self): - """ - Request is valid. - """ - request = { - # Raw trytes are extracted to match the IRI's JSON protocol. - 'tails': [self.hash1, self.hash2], - } - - filter_ = self._filter(request) - - self.assertFilterPasses(filter_) - self.assertDictEqual(filter_.cleaned_data, request) - - def test_pass_compatible_types(self): - """ - Request contains values that can be converted to the expected - types. - """ - filter_ = self._filter({ - 'tails': [ - # Any TrytesCompatible value can be used here. - TransactionHash(self.hash1), - bytearray(self.hash2.encode('ascii')), - ], - }) - - self.assertFilterPasses(filter_) - self.assertDictEqual( - filter_.cleaned_data, - - { - # Raw trytes are extracted to match the IRI's JSON protocol. - 'tails': [self.hash1, self.hash2], - }, - ) - - def test_fail_empty(self): - """ - Request is empty. - """ - self.assertFilterErrors( - {}, - - { - 'tails': [f.FilterMapper.CODE_MISSING_KEY], - }, - ) - - def test_fail_unexpected_parameters(self): - """ - Request contains unexpected parameters. - """ - self.assertFilterErrors( - { - 'tails': [TransactionHash(self.hash1)], - 'foo': 'bar', - }, - - { - 'foo': [f.FilterMapper.CODE_EXTRA_KEY], - }, - ) - - def test_fail_tails_null(self): - """ - ``tails`` is null. - """ - self.assertFilterErrors( - { - 'tails': None, - }, - - { - 'tails': [f.Required.CODE_EMPTY], - }, - ) - - def test_fail_tails_wrong_type(self): - """ - ``tails`` is not an array. - """ - self.assertFilterErrors( - { - # It's gotta be an array, even if there's only one hash. - 'tails': TransactionHash(self.hash1), - }, - - { - 'tails': [f.Type.CODE_WRONG_TYPE], - }, - ) - - def test_fail_tails_empty(self): - """ - ``tails`` is an array, but it is empty. - """ - self.assertFilterErrors( - { - 'tails': [], - }, - - { - 'tails': [f.Required.CODE_EMPTY], - }, - ) - - def test_fail_tails_contents_invalid(self): - """ - ``tails`` is a non-empty array, but it contains invalid values. - """ - self.assertFilterErrors( - { - 'tails': [ - b'', - True, - None, - b'not valid trytes', - - # This is actually valid; I just added it to make sure the - # filter isn't cheating! - TryteString(self.hash1), - - 2130706433, - b'9' * 82, - ], - }, - - { - 'tails.0': [f.Required.CODE_EMPTY], - 'tails.1': [f.Type.CODE_WRONG_TYPE], - 'tails.2': [f.Required.CODE_EMPTY], - 'tails.3': [Trytes.CODE_NOT_TRYTES], - 'tails.5': [f.Type.CODE_WRONG_TYPE], - 'tails.6': [Trytes.CODE_WRONG_FORMAT], - }, - ) + filter_type = CheckConsistencyCommand(MockAdapter()).get_request_filter + skip_value_check = True + + # noinspection SpellCheckingInspection + def setUp(self): + super(CheckConsistencyRequestFilterTestCase, self).setUp() + + self.hash1 = ( + 'TESTVALUE9DONTUSEINPRODUCTION99999DXSCAD' + 'YBVDCTTBLHFYQATFZPYPCBG9FOUKIGMYIGLHM9NEZ' + ) + + self.hash2 = ( + 'TESTVALUE9DONTUSEINPRODUCTION99999EMFYSM' + 'HWODIAPUTTFDLQRLYIDAUIPJXXEXZZSBVKZEBWGAN' + ) + + def test_pass_happy_path(self): + """ + Request is valid. + """ + request = { + # Raw trytes are extracted to match the IRI's JSON protocol. + 'tails': [self.hash1, self.hash2], + } + + filter_ = self._filter(request) + + self.assertFilterPasses(filter_) + self.assertDictEqual(filter_.cleaned_data, request) + + def test_pass_compatible_types(self): + """ + Request contains values that can be converted to the expected + types. + """ + filter_ = self._filter({ + 'tails': [ + # Any TrytesCompatible value can be used here. + TransactionHash(self.hash1), + bytearray(self.hash2.encode('ascii')), + ], + }) + + self.assertFilterPasses(filter_) + self.assertDictEqual( + filter_.cleaned_data, + + { + # Raw trytes are extracted to match the IRI's JSON protocol. + 'tails': [self.hash1, self.hash2], + }, + ) + + def test_fail_empty(self): + """ + Request is empty. + """ + self.assertFilterErrors( + {}, + + { + 'tails': [f.FilterMapper.CODE_MISSING_KEY], + }, + ) + + def test_fail_unexpected_parameters(self): + """ + Request contains unexpected parameters. + """ + self.assertFilterErrors( + { + 'tails': [TransactionHash(self.hash1)], + 'foo': 'bar', + }, + + { + 'foo': [f.FilterMapper.CODE_EXTRA_KEY], + }, + ) + + def test_fail_tails_null(self): + """ + ``tails`` is null. + """ + self.assertFilterErrors( + { + 'tails': None, + }, + + { + 'tails': [f.Required.CODE_EMPTY], + }, + ) + + def test_fail_tails_wrong_type(self): + """ + ``tails`` is not an array. + """ + self.assertFilterErrors( + { + # It's gotta be an array, even if there's only one hash. + 'tails': TransactionHash(self.hash1), + }, + + { + 'tails': [f.Type.CODE_WRONG_TYPE], + }, + ) + + def test_fail_tails_empty(self): + """ + ``tails`` is an array, but it is empty. + """ + self.assertFilterErrors( + { + 'tails': [], + }, + + { + 'tails': [f.Required.CODE_EMPTY], + }, + ) + + def test_fail_tails_contents_invalid(self): + """ + ``tails`` is a non-empty array, but it contains invalid values. + """ + self.assertFilterErrors( + { + 'tails': [ + b'', + True, + None, + b'not valid trytes', + + # This is actually valid; I just added it to make sure the + # filter isn't cheating! + TryteString(self.hash1), + + 2130706433, + b'9' * 82, + ], + }, + + { + 'tails.0': [f.Required.CODE_EMPTY], + 'tails.1': [f.Type.CODE_WRONG_TYPE], + 'tails.2': [f.Required.CODE_EMPTY], + 'tails.3': [Trytes.CODE_NOT_TRYTES], + 'tails.5': [f.Type.CODE_WRONG_TYPE], + 'tails.6': [Trytes.CODE_WRONG_FORMAT], + }, + ) + + +class CheckConsistencyResponseFilterTestCase(BaseFilterTestCase): + filter_type = CheckConsistencyCommand(MockAdapter()).get_response_filter + skip_value_check = True + + def test_check_consistency_is_true_response(self): + """ + Typical ``checkConsistency`` is true response + """ + filter_ = self._filter({ + 'state': True + }) + + self.assertFilterPasses(filter_) + self.assertDictEqual( + filter_.cleaned_data, + + { + 'state': True + }, + ) + + def test_check_consistency_is_false_response(self): + """ + Typical ``checkConsistency`` is false response + """ + filter_ = self._filter({ + 'state': False + }) + + self.assertFilterPasses(filter_) + self.assertDictEqual( + filter_.cleaned_data, + + { + 'state': False + }, + ) class CheckConsistencyCommandTestCase(TestCase): - # noinspection SpellCheckingInspection - def setUp(self): - super(CheckConsistencyCommandTestCase, self).setUp() - - self.adapter = MockAdapter() - self.command = CheckConsistencyCommand(self.adapter) - - # Define some tryte sequences that we can re-use across tests. - self.milestone =\ - TransactionHash( - b'TESTVALUE9DONTUSEINPRODUCTION99999W9KDIH' - b'BALAYAFCADIDU9HCXDKIXEYDNFRAKHN9IEIDZFWGJ' - ) - - self.hash1 =\ - TransactionHash( - b'TESTVALUE9DONTUSEINPRODUCTION99999TBPDM9' - b'ADFAWCKCSFUALFGETFIFG9UHIEFE9AYESEHDUBDDF' - ) - - self.hash2 =\ - TransactionHash( - b'TESTVALUE9DONTUSEINPRODUCTION99999CIGCCF' - b'KIUFZF9EP9YEYGQAIEXDTEAAUGAEWBBASHYCWBHDX' - ) - - def test_wireup(self): - """ - Verify that the command is wired up correctly. - """ - self.assertIsInstance( - Iota(self.adapter).checkConsistency, - CheckConsistencyCommand, - ) - - def test_happy_path(self): - """ - Successfully checking consistency. - """ - - self.adapter.seed_response('checkConsistency', { - 'state': True, - }) - - response = self.command(tails=[self.hash1, self.hash2]) - - self.assertDictEqual( - response, - - { - 'state': True, - } - ) - - def test_info_with_false_state(self): - """ - `info` field exists when `state` is False. - """ - - self.adapter.seed_response('checkConsistency', { - 'state': False, - 'info': 'Additional information', - }) - - response = self.command(tails=[self.hash1, self.hash2]) - - self.assertDictEqual( - response, - - { - 'state': False, - 'info': 'Additional information', - } - ) + # noinspection SpellCheckingInspection + def setUp(self): + super(CheckConsistencyCommandTestCase, self).setUp() + + self.adapter = MockAdapter() + self.command = CheckConsistencyCommand(self.adapter) + + # Define some tryte sequences that we can re-use across tests. + self.milestone = \ + TransactionHash( + b'TESTVALUE9DONTUSEINPRODUCTION99999W9KDIH' + b'BALAYAFCADIDU9HCXDKIXEYDNFRAKHN9IEIDZFWGJ' + ) + + self.hash1 = \ + TransactionHash( + b'TESTVALUE9DONTUSEINPRODUCTION99999TBPDM9' + b'ADFAWCKCSFUALFGETFIFG9UHIEFE9AYESEHDUBDDF' + ) + + self.hash2 = \ + TransactionHash( + b'TESTVALUE9DONTUSEINPRODUCTION99999CIGCCF' + b'KIUFZF9EP9YEYGQAIEXDTEAAUGAEWBBASHYCWBHDX' + ) + + def test_wireup(self): + """ + Verify that the command is wired up correctly. + """ + self.assertIsInstance( + Iota(self.adapter).checkConsistency, + CheckConsistencyCommand, + ) + + def test_happy_path(self): + """ + Successfully checking consistency. + """ + + self.adapter.seed_response('checkConsistency', { + 'state': True, + }) + + response = self.command(tails=[self.hash1, self.hash2]) + + self.assertDictEqual( + response, + + { + 'state': True, + } + ) + + def test_info_with_false_state(self): + """ + `info` field exists when `state` is False. + """ + + self.adapter.seed_response('checkConsistency', { + 'state': False, + 'info': 'Additional information', + }) + + response = self.command(tails=[self.hash1, self.hash2]) + + self.assertDictEqual( + response, + + { + 'state': False, + 'info': 'Additional information', + } + ) diff --git a/test/commands/core/get_balances_test.py b/test/commands/core/get_balances_test.py index 01245bd..e4071b9 100644 --- a/test/commands/core/get_balances_test.py +++ b/test/commands/core/get_balances_test.py @@ -1,6 +1,6 @@ # coding=utf-8 from __future__ import absolute_import, division, print_function, \ - unicode_literals + unicode_literals from unittest import TestCase @@ -14,290 +14,345 @@ class GetBalancesRequestFilterTestCase(BaseFilterTestCase): - filter_type = GetBalancesCommand(MockAdapter()).get_request_filter - skip_value_check = True - - # noinspection SpellCheckingInspection - def setUp(self): - super(GetBalancesRequestFilterTestCase, self).setUp() - - # Define a few valid values that we can reuse across tests. - self.trytes1 = ( - 'TESTVALUE9DONTUSEINPRODUCTION99999EKJZZT' - 'SOGJOUNVEWLDPKGTGAOIZIPMGBLHC9LMQNHLGXGYX' - ) - - self.trytes2 = ( - 'TESTVALUE9DONTUSEINPRODUCTION99999FDCDTZ' - 'ZWLL9MYGUTLSYVSIFJ9NGALTRMCQVIIOVEQOITYTE' - ) - - def test_pass_happy_path(self): - """ - Typical invocation of ``getBalances``. - """ - request = { - # Raw trytes are extracted to match the IRI's JSON protocol. - 'addresses': [self.trytes1, self.trytes2], - - 'threshold': 80, - } - - filter_ = self._filter(request) - - self.assertFilterPasses(filter_) - self.assertDictEqual(filter_.cleaned_data, request) - - def test_pass_compatible_types(self): - """ - The incoming request contains values that can be converted to the - expected types. - """ - request = { - 'addresses': [ - Address(self.trytes1), - bytearray(self.trytes2.encode('ascii')), - ], - - 'threshold': 80, - } - - filter_ = self._filter(request) - - self.assertFilterPasses(filter_) - self.assertDictEqual( - filter_.cleaned_data, - - { - 'addresses': [self.trytes1, self.trytes2], - 'threshold': 80, - }, - ) - - def test_pass_threshold_optional(self): - """ - The incoming request does not contain a ``threshold`` value, so the - default value is assumed. - """ - request = { - 'addresses': [Address(self.trytes1)], - } - - filter_ = self._filter(request) - - self.assertFilterPasses(filter_) - self.assertDictEqual( - filter_.cleaned_data, - - { - 'addresses': [Address(self.trytes1)], - 'threshold': 100, - }, - ) - - def test_fail_empty(self): - """ - The incoming request is empty. - """ - self.assertFilterErrors( - {}, - - { - 'addresses': [f.FilterMapper.CODE_MISSING_KEY], - }, - ) - - def test_fail_unexpected_parameters(self): - """ - The incoming request contains unexpected parameters. - """ - self.assertFilterErrors( - { - 'addresses': [Address(self.trytes1)], - - # I've had a perfectly wonderful evening. - # But this wasn't it. - 'foo': 'bar', - }, - - { - 'foo': [f.FilterMapper.CODE_EXTRA_KEY], - }, - ) - - def test_fail_addresses_wrong_type(self): - """ - ``addresses`` is not an array. - """ - self.assertFilterErrors( - { - 'addresses': Address(self.trytes1), - }, - - { - 'addresses': [f.Type.CODE_WRONG_TYPE], - }, - ) - - def test_fail_addresses_empty(self): - """ - ``addresses`` is an array, but it's empty. - """ - self.assertFilterErrors( - { - 'addresses': [], - }, - - { - 'addresses': [f.Required.CODE_EMPTY], - }, - ) - - def test_fail_addresses_contents_invalid(self): - """ - ``addresses`` is an array, but it contains invalid values. - """ - self.assertFilterErrors( - { - 'addresses': [ - b'', - True, - None, - b'not valid trytes', - - # This is actually valid; I just added it to make sure the - # filter isn't cheating! - TryteString(self.trytes2), - - 2130706433, - b'9' * 82, - ], - }, - - { - 'addresses.0': [f.Required.CODE_EMPTY], - 'addresses.1': [f.Type.CODE_WRONG_TYPE], - 'addresses.2': [f.Required.CODE_EMPTY], - 'addresses.3': [Trytes.CODE_NOT_TRYTES], - 'addresses.5': [f.Type.CODE_WRONG_TYPE], - 'addresses.6': [Trytes.CODE_WRONG_FORMAT], - }, - ) - - def test_fail_threshold_float(self): - """ - `threshold` is a float. - """ - self.assertFilterErrors( - { - # Even with an empty fpart, floats are not accepted. - 'threshold': 86.0, - - 'addresses': [Address(self.trytes1)], - }, - - { - 'threshold': [f.Type.CODE_WRONG_TYPE], - }, - ) - - def test_fail_threshold_string(self): - """ - ``threshold`` is a string. - """ - self.assertFilterErrors( - { - 'threshold': '86', - - 'addresses': [Address(self.trytes1)], - }, - - { - 'threshold': [f.Type.CODE_WRONG_TYPE], - }, - ) - - def test_fail_threshold_too_small(self): - """ - ``threshold`` is less than 0. - """ - self.assertFilterErrors( - { - 'threshold': -1, - - 'addresses': [Address(self.trytes1)], - }, - - { - 'threshold': [f.Min.CODE_TOO_SMALL], - }, - ) - - def test_fail_threshold_too_big(self): - """ - ``threshold`` is greater than 100. - """ - self.assertFilterErrors( - { - 'threshold': 101, - - 'addresses': [Address(self.trytes1)], - }, - - { - 'threshold': [f.Max.CODE_TOO_BIG], - }, - ) + filter_type = GetBalancesCommand(MockAdapter()).get_request_filter + skip_value_check = True + + # noinspection SpellCheckingInspection + def setUp(self): + super(GetBalancesRequestFilterTestCase, self).setUp() + + # Define a few valid values that we can reuse across tests. + self.trytes1 = ( + 'TESTVALUE9DONTUSEINPRODUCTION99999EKJZZT' + 'SOGJOUNVEWLDPKGTGAOIZIPMGBLHC9LMQNHLGXGYX' + ) + + self.trytes2 = ( + 'TESTVALUE9DONTUSEINPRODUCTION99999FDCDTZ' + 'ZWLL9MYGUTLSYVSIFJ9NGALTRMCQVIIOVEQOITYTE' + ) + + self.trytes3 = ( + 'TESTVALUE9DONTUSEINPRODUCTION99999FDCDTZ' + 'ASKDFJWOEFJSKLDJFWEIOFFJSKDJFWIOEFJSKDF9E' + ) + + def test_pass_happy_path(self): + """ + Typical invocation of ``getBalances``. + """ + request = { + # Raw trytes are extracted to match the IRI's JSON protocol. + 'addresses': [self.trytes1, self.trytes2], + + 'threshold': 80, + } + + filter_ = self._filter(request) + + self.assertFilterPasses(filter_) + self.assertDictEqual(filter_.cleaned_data, request) + + def test_pass_happy_path_with_tips(self): + """ + Typical invocation of ``getBalances`` with tips. + """ + request = { + 'addresses': [self.trytes1, self.trytes2], + + 'threshold': 80, + + 'tips': [self.trytes3, ], + } + + filter_ = self._filter(request) + + self.assertFilterPasses(filter_) + self.assertDictEqual(filter_.cleaned_data, request) + + def test_pass_compatible_types(self): + """ + The incoming request contains values that can be converted to the + expected types. + """ + request = { + 'addresses': [ + Address(self.trytes1), + bytearray(self.trytes2.encode('ascii')), + ], + + 'threshold': 80, + } + + filter_ = self._filter(request) + + self.assertFilterPasses(filter_) + self.assertDictEqual( + filter_.cleaned_data, + + { + 'addresses': [self.trytes1, self.trytes2], + 'threshold': 80, + }, + ) + + def test_pass_threshold_optional(self): + """ + The incoming request does not contain a ``threshold`` value, so the + default value is assumed. + """ + request = { + 'addresses': [Address(self.trytes1)], + } + + filter_ = self._filter(request) + + self.assertFilterPasses(filter_) + self.assertDictEqual( + filter_.cleaned_data, + + { + 'addresses': [Address(self.trytes1)], + 'threshold': 100, + }, + ) + + def test_fail_empty(self): + """ + The incoming request is empty. + """ + self.assertFilterErrors( + {}, + + { + 'addresses': [f.FilterMapper.CODE_MISSING_KEY], + }, + ) + + def test_fail_unexpected_parameters(self): + """ + The incoming request contains unexpected parameters. + """ + self.assertFilterErrors( + { + 'addresses': [Address(self.trytes1)], + + # I've had a perfectly wonderful evening. + # But this wasn't it. + 'foo': 'bar', + }, + + { + 'foo': [f.FilterMapper.CODE_EXTRA_KEY], + }, + ) + + def test_fail_addresses_wrong_type(self): + """ + ``addresses`` is not an array. + """ + self.assertFilterErrors( + { + 'addresses': Address(self.trytes1), + }, + + { + 'addresses': [f.Type.CODE_WRONG_TYPE], + }, + ) + + def test_fail_addresses_empty(self): + """ + ``addresses`` is an array, but it's empty. + """ + self.assertFilterErrors( + { + 'addresses': [], + }, + + { + 'addresses': [f.Required.CODE_EMPTY], + }, + ) + + def test_fail_addresses_contents_invalid(self): + """ + ``addresses`` is an array, but it contains invalid values. + """ + self.assertFilterErrors( + { + 'addresses': [ + b'', + True, + None, + b'not valid trytes', + + # This is actually valid; I just added it to make sure the + # filter isn't cheating! + TryteString(self.trytes2), + + 2130706433, + b'9' * 82, + ], + }, + + { + 'addresses.0': [f.Required.CODE_EMPTY], + 'addresses.1': [f.Type.CODE_WRONG_TYPE], + 'addresses.2': [f.Required.CODE_EMPTY], + 'addresses.3': [Trytes.CODE_NOT_TRYTES], + 'addresses.5': [f.Type.CODE_WRONG_TYPE], + 'addresses.6': [Trytes.CODE_WRONG_FORMAT], + }, + ) + + def test_fail_threshold_float(self): + """ + `threshold` is a float. + """ + self.assertFilterErrors( + { + # Even with an empty fpart, floats are not accepted. + 'threshold': 86.0, + + 'addresses': [Address(self.trytes1)], + }, + + { + 'threshold': [f.Type.CODE_WRONG_TYPE], + }, + ) + + def test_fail_threshold_string(self): + """ + ``threshold`` is a string. + """ + self.assertFilterErrors( + { + 'threshold': '86', + + 'addresses': [Address(self.trytes1)], + }, + + { + 'threshold': [f.Type.CODE_WRONG_TYPE], + }, + ) + + def test_fail_threshold_too_small(self): + """ + ``threshold`` is less than 0. + """ + self.assertFilterErrors( + { + 'threshold': -1, + + 'addresses': [Address(self.trytes1)], + }, + + { + 'threshold': [f.Min.CODE_TOO_SMALL], + }, + ) + + def test_fail_threshold_too_big(self): + """ + ``threshold`` is greater than 100. + """ + self.assertFilterErrors( + { + 'threshold': 101, + + 'addresses': [Address(self.trytes1)], + }, + + { + 'threshold': [f.Max.CODE_TOO_BIG], + }, + ) + + def test_fail_tips_contents_invalid(self): + """ + ``tips`` is an array, but it contains invalid values. + """ + self.assertFilterErrors( + { + 'addresses': [self.trytes1, ], + 'tips': [ + b'', + True, + None, + b'not valid trytes', + + # This is actually valid; I just added it to make sure the + # filter isn't cheating! + TryteString(self.trytes2), + + 2130706433, + b'9' * 82, + ], + }, + + { + 'tips.0': [f.Required.CODE_EMPTY], + 'tips.1': [f.Type.CODE_WRONG_TYPE], + 'tips.2': [f.Required.CODE_EMPTY], + 'tips.3': [Trytes.CODE_NOT_TRYTES], + 'tips.5': [f.Type.CODE_WRONG_TYPE], + 'tips.6': [Trytes.CODE_WRONG_FORMAT], + }, + ) # noinspection SpellCheckingInspection class GetBalancesResponseFilterTestCase(BaseFilterTestCase): - filter_type = GetBalancesCommand(MockAdapter()).get_response_filter - skip_value_check = True - - def test_balances(self): - """ - Typical ``getBalances`` response. - """ - filter_ = self._filter({ - 'balances': ['114544444', '0', '8175737'], - 'duration': 42, - 'milestoneIndex': 128, - - 'milestone': - 'INRTUYSZCWBHGFGGXXPWRWBZACYAFGVRRP9VYEQJ' - 'OHYD9URMELKWAFYFMNTSP9MCHLXRGAFMBOZPZ9999', - }) - - self.assertFilterPasses(filter_) - self.assertDictEqual( - filter_.cleaned_data, - - { - 'balances': [114544444, 0, 8175737], - 'duration': 42, - 'milestoneIndex': 128, - - 'milestone': - Address( - b'INRTUYSZCWBHGFGGXXPWRWBZACYAFGVRRP9VYEQJ' - b'OHYD9URMELKWAFYFMNTSP9MCHLXRGAFMBOZPZ9999', - ), - }, - ) + filter_type = GetBalancesCommand(MockAdapter()).get_response_filter + skip_value_check = True + + def setUp(self): + super(GetBalancesResponseFilterTestCase, self).setUp() + + # Define a few valid values that we can reuse across tests. + self.trytes1 = ( + 'TESTVALUE9DONTUSEINPRODUCTION99999EKJZZT' + 'SOGJOUNVEWLDPKGTGAOIZIPMGBLHC9LMQNHLGXGYX' + ) + + def test_balances(self): + """ + Typical ``getBalances`` response. + """ + filter_ = self._filter({ + 'balances': ['114544444', '0', '8175737'], + 'duration': 42, + 'milestoneIndex': 128, + 'references': [self.trytes1, ] + }) + + self.assertFilterPasses(filter_) + self.assertDictEqual( + filter_.cleaned_data, + + { + 'balances': [114544444, 0, 8175737], + 'duration': 42, + 'milestoneIndex': 128, + 'references': [self.trytes1, ] + }, + ) class GetBalancesCommandTestCase(TestCase): - def setUp(self): - super(GetBalancesCommandTestCase, self).setUp() - - self.adapter = MockAdapter() - - def test_wireup(self): - """ - Verify that the command is wired up correctly. - """ - self.assertIsInstance( - Iota(self.adapter).getBalances, - GetBalancesCommand, - ) + def setUp(self): + super(GetBalancesCommandTestCase, self).setUp() + + self.adapter = MockAdapter() + + def test_wireup(self): + """ + Verify that the command is wired up correctly. + """ + self.assertIsInstance( + Iota(self.adapter).getBalances, + GetBalancesCommand, + ) diff --git a/test/commands/core/get_missing_transactions_test.py b/test/commands/core/get_missing_transactions_test.py new file mode 100644 index 0000000..56575be --- /dev/null +++ b/test/commands/core/get_missing_transactions_test.py @@ -0,0 +1,116 @@ +# coding=utf-8 +from __future__ import absolute_import, division, print_function, \ + unicode_literals + +from unittest import TestCase + +import filters as f +from filters.test import BaseFilterTestCase + +from iota import Iota, TransactionHash +from iota.adapter import MockAdapter +from iota.commands.core import GetMissingTransactionsCommand + + +class GetMissingTransactionsFilterTestCase(BaseFilterTestCase): + filter_type = \ + GetMissingTransactionsCommand(MockAdapter()).get_request_filter + skip_value_check = True + + def test_pass_empty(self): + """ + The incoming response is (correctly) empty. + """ + request = {} + + filter_ = self._filter(request) + + self.assertFilterPasses(filter_) + self.assertDictEqual(filter_.cleaned_data, request) + + def test_fail_unexpected_parameters(self): + """ + The incoming response contains unexpected parameters. + """ + self.assertFilterErrors( + { + # All you had to do was nothing! How did you screw that up?! + 'foo': 'bar', + }, + + { + 'foo': [f.FilterMapper.CODE_EXTRA_KEY], + }, + ) + + +class GetMissingTransactionsResponseFilterTestCase(BaseFilterTestCase): + filter_type = \ + GetMissingTransactionsCommand(MockAdapter()).get_response_filter + skip_value_check = True + + # noinspection SpellCheckingInspection + def test_no_results(self): + """ + The incoming response contains no hashes. + """ + response = { + 'hashes': [], + } + + filter_ = self._filter(response) + + self.assertFilterPasses(filter_) + self.assertDictEqual(filter_.cleaned_data, response) + + # noinspection SpellCheckingInspection + def test_search_results(self): + """ + The incoming response contains lots of hashes. + """ + filter_ = self._filter({ + 'hashes': [ + 'RVORZ9SIIP9RCYMREUIXXVPQIPHVCNPQ9HZWYKFW' + 'YWZRE9JQKG9REPKIASHUUECPSQO9JT9XNMVKWYGVA', + + 'ZJVYUGTDRPDYFGFXMKOTV9ZWSGFK9CFPXTITQLQN' + 'LPPG9YNAARMKNKYQO9GSCSBIOTGMLJUFLZWSY9999', + ], + + }) + + self.assertFilterPasses(filter_) + self.assertDictEqual( + filter_.cleaned_data, + + { + 'hashes': [ + TransactionHash( + b'RVORZ9SIIP9RCYMREUIXXVPQIPHVCNPQ9HZWYKFW' + b'YWZRE9JQKG9REPKIASHUUECPSQO9JT9XNMVKWYGVA', + ), + + TransactionHash( + b'ZJVYUGTDRPDYFGFXMKOTV9ZWSGFK9CFPXTITQLQN' + b'LPPG9YNAARMKNKYQO9GSCSBIOTGMLJUFLZWSY9999', + ), + ], + + }, + ) + + +class GetMissingTransactionsCommandTestCase(TestCase): + def setUp(self): + super(GetMissingTransactionsCommandTestCase, self).setUp() + + self.adapter = MockAdapter() + + def test_wireup(self): + """ + Verify that the command is wired up correctly. + """ + self.assertIsInstance( + Iota(self.adapter).getMissingTransactions, + GetMissingTransactionsCommand, + ) diff --git a/test/commands/core/get_node_api_configuration_test.py b/test/commands/core/get_node_api_configuration_test.py new file mode 100644 index 0000000..da2c0ed --- /dev/null +++ b/test/commands/core/get_node_api_configuration_test.py @@ -0,0 +1,95 @@ +# coding=utf-8 +from __future__ import absolute_import, division, print_function, \ + unicode_literals + +from unittest import TestCase + +import filters as f +from filters.test import BaseFilterTestCase + +from iota import Iota +from iota.adapter import MockAdapter +from iota.commands.core import GetNodeAPIConfigurationCommand + + +class GetNodeAPIConfigurationRequestFilterTestCase(BaseFilterTestCase): + filter_type = \ + GetNodeAPIConfigurationCommand(MockAdapter()).get_request_filter + skip_value_check = True + + def test_pass_empty(self): + """ + The incoming response is (correctly) empty. + """ + request = {} + + filter_ = self._filter(request) + + self.assertFilterPasses(filter_) + self.assertDictEqual(filter_.cleaned_data, request) + + def test_fail_unexpected_parameters(self): + """ + The incoming response contains unexpected parameters. + """ + self.assertFilterErrors( + { + # All you had to do was nothing! How did you screw that up?! + 'foo': 'bar', + }, + + { + 'foo': [f.FilterMapper.CODE_EXTRA_KEY], + }, + ) + + +class GetNodeAPIConfigurationFilterTestCase(BaseFilterTestCase): + filter_type = \ + GetNodeAPIConfigurationCommand(MockAdapter()).get_response_filter + skip_value_check = True + + # noinspection SpellCheckingInspection + def test_pass_happy_path(self): + """ + The incoming response contains valid values. + """ + response = { + 'maxFindTransactions': 100000, + 'maxRequestsList': 1000, + 'maxGetTrytes': 10000, + 'maxBodyLength': 1000000, + 'testNet': False, + 'milestoneStartIndex': 1050000, + } + + filter_ = self._filter(response) + + self.assertFilterPasses(filter_) + self.assertDictEqual( + filter_.cleaned_data, + { + 'maxFindTransactions': 100000, + 'maxRequestsList': 1000, + 'maxGetTrytes': 10000, + 'maxBodyLength': 1000000, + 'testNet': False, + 'milestoneStartIndex': 1050000, + } + ) + + +class GetNodeAPIConfigurationCommandTestCase(TestCase): + def setUp(self): + super(GetNodeAPIConfigurationCommandTestCase, self).setUp() + + self.adapter = MockAdapter() + + def test_wireup(self): + """ + Verify that the command is wired up correctly. + """ + self.assertIsInstance( + Iota(self.adapter).getNodeAPIConfiguration, + GetNodeAPIConfigurationCommand, + ) diff --git a/test/commands/core/get_transactions_to_approve_test.py b/test/commands/core/get_transactions_to_approve_test.py index 5875fdc..89559bf 100644 --- a/test/commands/core/get_transactions_to_approve_test.py +++ b/test/commands/core/get_transactions_to_approve_test.py @@ -1,6 +1,6 @@ # coding=utf-8 from __future__ import absolute_import, division, print_function, \ - unicode_literals + unicode_literals from unittest import TestCase @@ -9,226 +9,226 @@ from iota import Iota, TransactionHash from iota.adapter import MockAdapter from iota.commands.core.get_transactions_to_approve import \ - GetTransactionsToApproveCommand + GetTransactionsToApproveCommand from iota.filters import Trytes class GetTransactionsToApproveRequestFilterTestCase(BaseFilterTestCase): - filter_type =\ - GetTransactionsToApproveCommand(MockAdapter()).get_request_filter - skip_value_check = True - - def setUp(self): - super(GetTransactionsToApproveRequestFilterTestCase, self).setUp() - - # Define some tryte sequences that we can reuse between tests. - self.trytes1 = ( - b'TESTVALUEONE9DONTUSEINPRODUCTION99999JBW' - b'GEC99GBXFFBCHAEJHLC9DX9EEPAI9ICVCKBX9FFII' - ) - - def test_pass_happy_path_without_reference(self): - """ - Request is valid without reference. - """ - request = { - 'depth': 100, - } - - filter_ = self._filter(request) - - self.assertFilterPasses(filter_) - self.assertDictEqual(filter_.cleaned_data, request) - - def test_pass_happy_path_with_reference(self): - """ - Request is valid with reference. - """ - request = { - 'depth': 100, - 'reference': TransactionHash(self.trytes1), - } - - filter_ = self._filter(request) - - self.assertFilterPasses(filter_) - self.assertDictEqual(filter_.cleaned_data, request) - - def test_fail_empty(self): - """ - Request is empty. - """ - self.assertFilterErrors( - {}, - - { - 'depth': [f.FilterMapper.CODE_MISSING_KEY], - }, - ) - - def test_fail_unexpected_parameters(self): - """ - Request contains unexpected parameters. - """ - self.assertFilterErrors( - { - 'depth': 100, - - # I knew I should have taken that left turn at Albuquerque. - 'foo': 'bar', - }, - - { - 'foo': [f.FilterMapper.CODE_EXTRA_KEY], - }, - ) - - def test_fail_depth_null(self): - """ - ``depth`` is null. - """ - self.assertFilterErrors( - { - 'depth': None, - }, - - { - 'depth': [f.Required.CODE_EMPTY], - }, - ) - - def test_fail_depth_float(self): - """ - ``depth`` is a float. - """ - self.assertFilterErrors( - { - 'depth': 100.0, - }, - - { - 'depth': [f.Type.CODE_WRONG_TYPE], - }, - ) - - def test_fail_depth_string(self): - """ - ``depth`` is a string. - """ - self.assertFilterErrors( - { - 'depth': '100', - }, - - { - 'depth': [f.Type.CODE_WRONG_TYPE], - }, - ) - - def test_fail_depth_too_small(self): - """ - ``depth`` is less than 1. - """ - self.assertFilterErrors( - { - 'depth': 0, - }, - - { - 'depth': [f.Min.CODE_TOO_SMALL], - }, - ) - - def test_fail_reference_wrong_type(self): - """ - ``reference`` is not a TrytesCompatible value. - """ - self.assertFilterErrors( - { - 'reference': 42, - - 'depth': 100, - }, - - { - 'reference': [f.Type.CODE_WRONG_TYPE], - }, - ) - - def test_fail_reference_not_trytes(self): - """ - ``reference`` contains invalid characters. - """ - self.assertFilterErrors( - { - 'reference': b'not valid; must contain only uppercase and "9"', - - 'depth': 100, - }, - - { - 'reference': [Trytes.CODE_NOT_TRYTES], - }, - ) + filter_type = \ + GetTransactionsToApproveCommand(MockAdapter()).get_request_filter + skip_value_check = True + + def setUp(self): + super(GetTransactionsToApproveRequestFilterTestCase, self).setUp() + + # Define some tryte sequences that we can reuse between tests. + self.trytes1 = ( + b'TESTVALUEONE9DONTUSEINPRODUCTION99999JBW' + b'GEC99GBXFFBCHAEJHLC9DX9EEPAI9ICVCKBX9FFII' + ) + + def test_pass_happy_path_without_reference(self): + """ + Request is valid without reference. + """ + request = { + 'depth': 100, + } + + filter_ = self._filter(request) + + self.assertFilterPasses(filter_) + self.assertDictEqual(filter_.cleaned_data, request) + + def test_pass_happy_path_with_reference(self): + """ + Request is valid with reference. + """ + request = { + 'depth': 100, + 'reference': TransactionHash(self.trytes1), + } + + filter_ = self._filter(request) + + self.assertFilterPasses(filter_) + self.assertDictEqual(filter_.cleaned_data, request) + + def test_fail_empty(self): + """ + Request is empty. + """ + self.assertFilterErrors( + {}, + + { + 'depth': [f.FilterMapper.CODE_MISSING_KEY], + }, + ) + + def test_fail_unexpected_parameters(self): + """ + Request contains unexpected parameters. + """ + self.assertFilterErrors( + { + 'depth': 100, + + # I knew I should have taken that left turn at Albuquerque. + 'foo': 'bar', + }, + + { + 'foo': [f.FilterMapper.CODE_EXTRA_KEY], + }, + ) + + def test_fail_depth_null(self): + """ + ``depth`` is null. + """ + self.assertFilterErrors( + { + 'depth': None, + }, + + { + 'depth': [f.Required.CODE_EMPTY], + }, + ) + + def test_fail_depth_float(self): + """ + ``depth`` is a float. + """ + self.assertFilterErrors( + { + 'depth': 100.0, + }, + + { + 'depth': [f.Type.CODE_WRONG_TYPE], + }, + ) + + def test_fail_depth_string(self): + """ + ``depth`` is a string. + """ + self.assertFilterErrors( + { + 'depth': '100', + }, + + { + 'depth': [f.Type.CODE_WRONG_TYPE], + }, + ) + + def test_fail_depth_too_small(self): + """ + ``depth`` is less than 1. + """ + self.assertFilterErrors( + { + 'depth': 0, + }, + + { + 'depth': [f.Min.CODE_TOO_SMALL], + }, + ) + + def test_fail_reference_wrong_type(self): + """ + ``reference`` is not a TrytesCompatible value. + """ + self.assertFilterErrors( + { + 'reference': 42, + + 'depth': 100, + }, + + { + 'reference': [f.Type.CODE_WRONG_TYPE], + }, + ) + + def test_fail_reference_not_trytes(self): + """ + ``reference`` contains invalid characters. + """ + self.assertFilterErrors( + { + 'reference': b'not valid; must contain only uppercase and "9"', + + 'depth': 100, + }, + + { + 'reference': [Trytes.CODE_NOT_TRYTES], + }, + ) class GetTransactionsToApproveResponseFilterTestCase(BaseFilterTestCase): - filter_type =\ - GetTransactionsToApproveCommand(MockAdapter()).get_response_filter - skip_value_check = True - - # noinspection SpellCheckingInspection - def test_pass_happy_path(self): - """ - Typical ``getTransactionsToApprove`` response. - """ - response = { - 'trunkTransaction': - 'TKGDZ9GEI9CPNQGHEATIISAKYPPPSXVCXBSR9EIW' - 'CTHHSSEQCD9YLDPEXYERCNJVASRGWMAVKFQTC9999', - - 'branchTransaction': - 'TKGDZ9GEI9CPNQGHEATIISAKYPPPSXVCXBSR9EIW' - 'CTHHSSEQCD9YLDPEXYERCNJVASRGWMAVKFQTC9999', - - 'duration': 936, - } - - filter_ = self._filter(response) - - self.assertFilterPasses(filter_) - self.assertDictEqual( - filter_.cleaned_data, - - { - 'trunkTransaction': - TransactionHash( - b'TKGDZ9GEI9CPNQGHEATIISAKYPPPSXVCXBSR9EIW' - b'CTHHSSEQCD9YLDPEXYERCNJVASRGWMAVKFQTC9999' - ), - - 'branchTransaction': - TransactionHash( - b'TKGDZ9GEI9CPNQGHEATIISAKYPPPSXVCXBSR9EIW' - b'CTHHSSEQCD9YLDPEXYERCNJVASRGWMAVKFQTC9999' - ), - - 'duration': 936, - }, - ) + filter_type = \ + GetTransactionsToApproveCommand(MockAdapter()).get_response_filter + skip_value_check = True + + # noinspection SpellCheckingInspection + def test_pass_happy_path(self): + """ + Typical ``getTransactionsToApprove`` response. + """ + response = { + 'trunkTransaction': + 'TKGDZ9GEI9CPNQGHEATIISAKYPPPSXVCXBSR9EIW' + 'CTHHSSEQCD9YLDPEXYERCNJVASRGWMAVKFQTC9999', + + 'branchTransaction': + 'TKGDZ9GEI9CPNQGHEATIISAKYPPPSXVCXBSR9EIW' + 'CTHHSSEQCD9YLDPEXYERCNJVASRGWMAVKFQTC9999', + + 'duration': 936, + } + + filter_ = self._filter(response) + + self.assertFilterPasses(filter_) + self.assertDictEqual( + filter_.cleaned_data, + + { + 'trunkTransaction': + TransactionHash( + b'TKGDZ9GEI9CPNQGHEATIISAKYPPPSXVCXBSR9EIW' + b'CTHHSSEQCD9YLDPEXYERCNJVASRGWMAVKFQTC9999' + ), + + 'branchTransaction': + TransactionHash( + b'TKGDZ9GEI9CPNQGHEATIISAKYPPPSXVCXBSR9EIW' + b'CTHHSSEQCD9YLDPEXYERCNJVASRGWMAVKFQTC9999' + ), + + 'duration': 936, + }, + ) class GetTransactionsToApproveTestCase(TestCase): - def setUp(self): - super(GetTransactionsToApproveTestCase, self).setUp() - - self.adapter = MockAdapter() - - def test_wireup(self): - """ - Verify that the command is wired up correctly. - """ - self.assertIsInstance( - Iota(self.adapter).getTransactionsToApprove, - GetTransactionsToApproveCommand, - ) + def setUp(self): + super(GetTransactionsToApproveTestCase, self).setUp() + + self.adapter = MockAdapter() + + def test_wireup(self): + """ + Verify that the command is wired up correctly. + """ + self.assertIsInstance( + Iota(self.adapter).getTransactionsToApprove, + GetTransactionsToApproveCommand, + ) diff --git a/test/commands/core/were_addresses_spent_from_test.py b/test/commands/core/were_addresses_spent_from_test.py index ba3053c..65d4cad 100644 --- a/test/commands/core/were_addresses_spent_from_test.py +++ b/test/commands/core/were_addresses_spent_from_test.py @@ -1,6 +1,6 @@ # coding=utf-8 from __future__ import absolute_import, division, print_function, \ - unicode_literals + unicode_literals from unittest import TestCase @@ -9,167 +9,208 @@ from iota import Address, Iota, TryteString from iota.adapter import MockAdapter -from iota.commands.core.were_addresses_spent_from import WereAddressesSpentFromCommand +from iota.commands.core import WereAddressesSpentFromCommand from iota.filters import Trytes class WereAddressesSpentFromRequestFilterTestCase(BaseFilterTestCase): - filter_type = WereAddressesSpentFromCommand(MockAdapter()).get_request_filter - skip_value_check = True - - # noinspection SpellCheckingInspection - def setUp(self): - super(WereAddressesSpentFromRequestFilterTestCase, self).setUp() - - # Define a few valid values that we can reuse across tests. - self.trytes1 = ( - 'TESTVALUE9DONTUSEINPRODUCTION99999EKJZZT' - 'SOGJOUNVEWLDPKGTGAOIZIPMGBLHC9LMQNHLGXGYX' - ) - - self.trytes2 = ( - 'TESTVALUE9DONTUSEINPRODUCTION99999FDCDTZ' - 'ZWLL9MYGUTLSYVSIFJ9NGALTRMCQVIIOVEQOITYTE' - ) - - def test_pass_happy_path(self): - """ - Typical invocation of ``wereAddressesSpentFrom``. - """ - request = { - # Raw trytes are extracted to match the IRI's JSON protocol. - 'addresses': [self.trytes1, self.trytes2], - } - - filter_ = self._filter(request) - - self.assertFilterPasses(filter_) - self.assertDictEqual(filter_.cleaned_data, request) - - def test_pass_compatible_types(self): - """ - The incoming request contains values that can be converted to the - expected types. - """ - request = { - 'addresses': [ - Address(self.trytes1), - bytearray(self.trytes2.encode('ascii')), - ], - } - - filter_ = self._filter(request) - - self.assertFilterPasses(filter_) - self.assertDictEqual( - filter_.cleaned_data, - - { - 'addresses': [self.trytes1, self.trytes2], - }, - ) - - def test_fail_empty(self): - """ - The incoming request is empty. - """ - self.assertFilterErrors( - {}, - - { - 'addresses': [f.FilterMapper.CODE_MISSING_KEY], - }, - ) - - def test_fail_unexpected_parameters(self): - """ - The incoming request contains unexpected parameters. - """ - self.assertFilterErrors( - { - 'addresses': [Address(self.trytes1)], - - # I've had a perfectly wonderful evening. - # But this wasn't it. - 'foo': 'bar', - }, - - { - 'foo': [f.FilterMapper.CODE_EXTRA_KEY], - }, - ) - - def test_fail_addresses_wrong_type(self): - """ - ``addresses`` is not an array. - """ - self.assertFilterErrors( - { - 'addresses': Address(self.trytes1), - }, - - { - 'addresses': [f.Type.CODE_WRONG_TYPE], - }, - ) - - def test_fail_addresses_empty(self): - """ - ``addresses`` is an array, but it's empty. - """ - self.assertFilterErrors( - { - 'addresses': [], - }, - - { - 'addresses': [f.Required.CODE_EMPTY], - }, - ) - - def test_fail_addresses_contents_invalid(self): - """ - ``addresses`` is an array, but it contains invalid values. - """ - self.assertFilterErrors( - { - 'addresses': [ - b'', - True, - None, - b'not valid trytes', - - # This is actually valid; I just added it to make sure the - # filter isn't cheating! - TryteString(self.trytes2), - - 2130706433, - b'9' * 82, - ], - }, - - { - 'addresses.0': [f.Required.CODE_EMPTY], - 'addresses.1': [f.Type.CODE_WRONG_TYPE], - 'addresses.2': [f.Required.CODE_EMPTY], - 'addresses.3': [Trytes.CODE_NOT_TRYTES], - 'addresses.5': [f.Type.CODE_WRONG_TYPE], - 'addresses.6': [Trytes.CODE_WRONG_FORMAT], - }, - ) + filter_type = \ + WereAddressesSpentFromCommand(MockAdapter()).get_request_filter + skip_value_check = True + + # noinspection SpellCheckingInspection + def setUp(self): + super(WereAddressesSpentFromRequestFilterTestCase, self).setUp() + + # Define a few valid values that we can reuse across tests. + self.trytes1 = ( + 'TESTVALUE9DONTUSEINPRODUCTION99999EKJZZT' + 'SOGJOUNVEWLDPKGTGAOIZIPMGBLHC9LMQNHLGXGYX' + ) + + self.trytes2 = ( + 'TESTVALUE9DONTUSEINPRODUCTION99999FDCDTZ' + 'ZWLL9MYGUTLSYVSIFJ9NGALTRMCQVIIOVEQOITYTE' + ) + + def test_pass_happy_path(self): + """ + Typical invocation of ``wereAddressesSpentFrom``. + """ + request = { + # Raw trytes are extracted to match the IRI's JSON protocol. + 'addresses': [self.trytes1, self.trytes2], + } + + filter_ = self._filter(request) + + self.assertFilterPasses(filter_) + self.assertDictEqual(filter_.cleaned_data, request) + + def test_pass_compatible_types(self): + """ + The incoming request contains values that can be converted to the + expected types. + """ + request = { + 'addresses': [ + Address(self.trytes1), + bytearray(self.trytes2.encode('ascii')), + ], + } + + filter_ = self._filter(request) + + self.assertFilterPasses(filter_) + self.assertDictEqual( + filter_.cleaned_data, + + { + 'addresses': [self.trytes1, self.trytes2], + }, + ) + + def test_fail_empty(self): + """ + The incoming request is empty. + """ + self.assertFilterErrors( + {}, + + { + 'addresses': [f.FilterMapper.CODE_MISSING_KEY], + }, + ) + + def test_fail_unexpected_parameters(self): + """ + The incoming request contains unexpected parameters. + """ + self.assertFilterErrors( + { + 'addresses': [Address(self.trytes1)], + + # I've had a perfectly wonderful evening. + # But this wasn't it. + 'foo': 'bar', + }, + + { + 'foo': [f.FilterMapper.CODE_EXTRA_KEY], + }, + ) + + def test_fail_addresses_wrong_type(self): + """ + ``addresses`` is not an array. + """ + self.assertFilterErrors( + { + 'addresses': Address(self.trytes1), + }, + + { + 'addresses': [f.Type.CODE_WRONG_TYPE], + }, + ) + + def test_fail_addresses_empty(self): + """ + ``addresses`` is an array, but it's empty. + """ + self.assertFilterErrors( + { + 'addresses': [], + }, + + { + 'addresses': [f.Required.CODE_EMPTY], + }, + ) + + def test_fail_addresses_contents_invalid(self): + """ + ``addresses`` is an array, but it contains invalid values. + """ + self.assertFilterErrors( + { + 'addresses': [ + b'', + True, + None, + b'not valid trytes', + + # This is actually valid; I just added it to make sure the + # filter isn't cheating! + TryteString(self.trytes2), + + 2130706433, + b'9' * 82, + ], + }, + + { + 'addresses.0': [f.Required.CODE_EMPTY], + 'addresses.1': [f.Type.CODE_WRONG_TYPE], + 'addresses.2': [f.Required.CODE_EMPTY], + 'addresses.3': [Trytes.CODE_NOT_TRYTES], + 'addresses.5': [f.Type.CODE_WRONG_TYPE], + 'addresses.6': [Trytes.CODE_WRONG_FORMAT], + }, + ) + + +class WereAddressesSpentFromResponseFilterTestCase(BaseFilterTestCase): + filter_type = \ + WereAddressesSpentFromCommand(MockAdapter()).get_response_filter + skip_value_check = True + + def test_single_address(self): + """ + Typical ``wereAddressesSpentFrom`` response for a single address + """ + filter_ = self._filter({ + 'states': [True, ] + }) + + self.assertFilterPasses(filter_) + self.assertDictEqual( + filter_.cleaned_data, + + { + 'states': [True, ] + }, + ) + + def test_multiple_addresses(self): + """ + Typical ``wereAddressesSpentFrom`` response for a multiple address + """ + filter_ = self._filter({ + 'states': [True, False, False, True] + }) + + self.assertFilterPasses(filter_) + self.assertDictEqual( + filter_.cleaned_data, + + { + 'states': [True, False, False, True] + }, + ) class WereAddressesSpentFromCommandTestCase(TestCase): - def setUp(self): - super(WereAddressesSpentFromCommandTestCase, self).setUp() - - self.adapter = MockAdapter() - - def test_wireup(self): - """ - Verify that the command is wired up correctly. - """ - self.assertIsInstance( - Iota(self.adapter).wereAddressesSpentFrom, - WereAddressesSpentFromCommand, - ) + def setUp(self): + super(WereAddressesSpentFromCommandTestCase, self).setUp() + + self.adapter = MockAdapter() + + def test_wireup(self): + """ + Verify that the command is wired up correctly. + """ + self.assertIsInstance( + Iota(self.adapter).wereAddressesSpentFrom, + WereAddressesSpentFromCommand, + ) From 57ae379745aea425df53335095f42e527c3a4ef8 Mon Sep 17 00:00:00 2001 From: Philipp de Col Date: Sat, 28 Sep 2019 19:02:20 +0200 Subject: [PATCH 6/8] [#235] Remove the response filters except for bundle and transactions hashes --- iota/commands/core/check_consistency.py | 11 +---- iota/commands/core/get_balances.py | 2 - .../core/get_node_api_configuration.py | 18 +-------- .../core/were_addresses_spent_from.py | 15 +------ test/commands/core/check_consistency_test.py | 39 ------------------ test/commands/core/get_balances_test.py | 8 ++-- .../core/get_missing_transactions_test.py | 6 +-- .../core/get_node_api_configuration_test.py | 39 +----------------- test/commands/core/get_node_info_test.py | 4 +- test/commands/core/get_tips_test.py | 4 +- .../core/were_addresses_spent_from_test.py | 40 ------------------- 11 files changed, 19 insertions(+), 167 deletions(-) diff --git a/iota/commands/core/check_consistency.py b/iota/commands/core/check_consistency.py index 054a2b3..598322e 100644 --- a/iota/commands/core/check_consistency.py +++ b/iota/commands/core/check_consistency.py @@ -5,7 +5,7 @@ import filters as f from iota import TransactionHash -from iota.commands import FilterCommand, RequestFilter, ResponseFilter +from iota.commands import FilterCommand, RequestFilter from iota.filters import Trytes __all__ = [ @@ -25,7 +25,7 @@ def get_request_filter(self): return CheckConsistencyRequestFilter() def get_response_filter(self): - return CheckConsistencyResponseFilter() + pass class CheckConsistencyRequestFilter(RequestFilter): @@ -36,10 +36,3 @@ def __init__(self): f.Array | f.FilterRepeater(f.Required | Trytes(TransactionHash)), }) - - -class CheckConsistencyResponseFilter(ResponseFilter): - def __init__(self): - super(CheckConsistencyResponseFilter, self).__init__({ - 'state': f.Required | f.Type(bool), - }) diff --git a/iota/commands/core/get_balances.py b/iota/commands/core/get_balances.py index 00c8c93..97f11c2 100644 --- a/iota/commands/core/get_balances.py +++ b/iota/commands/core/get_balances.py @@ -83,8 +83,6 @@ def __init__(self): super(GetBalancesResponseFilter, self).__init__({ 'balances': f.Array | f.FilterRepeater(f.Int), - 'milestoneIndex': f.Int, - 'references': f.Array | f.FilterRepeater( f.Required | diff --git a/iota/commands/core/get_node_api_configuration.py b/iota/commands/core/get_node_api_configuration.py index 60475fa..acaf9a0 100644 --- a/iota/commands/core/get_node_api_configuration.py +++ b/iota/commands/core/get_node_api_configuration.py @@ -2,9 +2,7 @@ from __future__ import absolute_import, division, print_function, \ unicode_literals -import filters as f - -from iota.commands import FilterCommand, RequestFilter, ResponseFilter +from iota.commands import FilterCommand, RequestFilter __all__ = [ 'GetNodeAPIConfigurationCommand', @@ -23,7 +21,7 @@ def get_request_filter(self): return GetNodeAPIConfigurationRequestFilter() def get_response_filter(self): - return GetNodeAPIConfigurationResponseFilter() + pass class GetNodeAPIConfigurationRequestFilter(RequestFilter): @@ -31,15 +29,3 @@ def __init__(self): # ``getNodeAPIConfiguration`` does not accept any parameters. # Using a filter here just to enforce that the request is empty. super(GetNodeAPIConfigurationRequestFilter, self).__init__({}) - - -class GetNodeAPIConfigurationResponseFilter(ResponseFilter): - def __init__(self): - super(GetNodeAPIConfigurationResponseFilter, self).__init__({ - 'maxFindTransactions': f.Type(int), - 'maxRequestsList': f.Type(int), - 'maxGetTrytes': f.Type(int), - 'maxBodyLength': f.Type(int), - 'testNet': f.Type(bool), - 'milestoneStartIndex': f.Type(int), - }) diff --git a/iota/commands/core/were_addresses_spent_from.py b/iota/commands/core/were_addresses_spent_from.py index 594e6c5..0d41e17 100644 --- a/iota/commands/core/were_addresses_spent_from.py +++ b/iota/commands/core/were_addresses_spent_from.py @@ -4,7 +4,7 @@ import filters as f -from iota.commands import FilterCommand, RequestFilter, ResponseFilter +from iota.commands import FilterCommand, RequestFilter from iota.filters import AddressNoChecksum __all__ = [ @@ -24,7 +24,7 @@ def get_request_filter(self): return WereAddressesSpentFromRequestFilter() def get_response_filter(self): - return WereAddressesSpentFromResponseFilter() + pass class WereAddressesSpentFromRequestFilter(RequestFilter): @@ -37,14 +37,3 @@ def __init__(self): f.Unicode(encoding='ascii', normalize=False), ), }) - - -class WereAddressesSpentFromResponseFilter(ResponseFilter): - def __init__(self): - super(WereAddressesSpentFromResponseFilter, self).__init__({ - 'states': - f.Array | f.FilterRepeater( - f.Required | - f.Type(bool), - ), - }) diff --git a/test/commands/core/check_consistency_test.py b/test/commands/core/check_consistency_test.py index e994ccd..028f39c 100644 --- a/test/commands/core/check_consistency_test.py +++ b/test/commands/core/check_consistency_test.py @@ -170,45 +170,6 @@ def test_fail_tails_contents_invalid(self): ) -class CheckConsistencyResponseFilterTestCase(BaseFilterTestCase): - filter_type = CheckConsistencyCommand(MockAdapter()).get_response_filter - skip_value_check = True - - def test_check_consistency_is_true_response(self): - """ - Typical ``checkConsistency`` is true response - """ - filter_ = self._filter({ - 'state': True - }) - - self.assertFilterPasses(filter_) - self.assertDictEqual( - filter_.cleaned_data, - - { - 'state': True - }, - ) - - def test_check_consistency_is_false_response(self): - """ - Typical ``checkConsistency`` is false response - """ - filter_ = self._filter({ - 'state': False - }) - - self.assertFilterPasses(filter_) - self.assertDictEqual( - filter_.cleaned_data, - - { - 'state': False - }, - ) - - class CheckConsistencyCommandTestCase(TestCase): # noinspection SpellCheckingInspection def setUp(self): diff --git a/test/commands/core/get_balances_test.py b/test/commands/core/get_balances_test.py index e4071b9..4f9ecc8 100644 --- a/test/commands/core/get_balances_test.py +++ b/test/commands/core/get_balances_test.py @@ -62,7 +62,7 @@ def test_pass_happy_path_with_tips(self): 'threshold': 80, - 'tips': [self.trytes3, ], + 'tips': [self.trytes3], } filter_ = self._filter(request) @@ -277,7 +277,7 @@ def test_fail_tips_contents_invalid(self): """ self.assertFilterErrors( { - 'addresses': [self.trytes1, ], + 'addresses': [self.trytes1], 'tips': [ b'', True, @@ -326,7 +326,7 @@ def test_balances(self): 'balances': ['114544444', '0', '8175737'], 'duration': 42, 'milestoneIndex': 128, - 'references': [self.trytes1, ] + 'references': [self.trytes1] }) self.assertFilterPasses(filter_) @@ -337,7 +337,7 @@ def test_balances(self): 'balances': [114544444, 0, 8175737], 'duration': 42, 'milestoneIndex': 128, - 'references': [self.trytes1, ] + 'references': [self.trytes1] }, ) diff --git a/test/commands/core/get_missing_transactions_test.py b/test/commands/core/get_missing_transactions_test.py index 56575be..cb6de74 100644 --- a/test/commands/core/get_missing_transactions_test.py +++ b/test/commands/core/get_missing_transactions_test.py @@ -12,14 +12,14 @@ from iota.commands.core import GetMissingTransactionsCommand -class GetMissingTransactionsFilterTestCase(BaseFilterTestCase): +class GetMissingTransactionsRequestFilterTestCase(BaseFilterTestCase): filter_type = \ GetMissingTransactionsCommand(MockAdapter()).get_request_filter skip_value_check = True def test_pass_empty(self): """ - The incoming response is (correctly) empty. + The incoming request is (correctly) empty. """ request = {} @@ -30,7 +30,7 @@ def test_pass_empty(self): def test_fail_unexpected_parameters(self): """ - The incoming response contains unexpected parameters. + The incoming request contains unexpected parameters. """ self.assertFilterErrors( { diff --git a/test/commands/core/get_node_api_configuration_test.py b/test/commands/core/get_node_api_configuration_test.py index da2c0ed..bee9e7a 100644 --- a/test/commands/core/get_node_api_configuration_test.py +++ b/test/commands/core/get_node_api_configuration_test.py @@ -19,7 +19,7 @@ class GetNodeAPIConfigurationRequestFilterTestCase(BaseFilterTestCase): def test_pass_empty(self): """ - The incoming response is (correctly) empty. + The incoming request is (correctly) empty. """ request = {} @@ -30,7 +30,7 @@ def test_pass_empty(self): def test_fail_unexpected_parameters(self): """ - The incoming response contains unexpected parameters. + The incoming request contains unexpected parameters. """ self.assertFilterErrors( { @@ -44,41 +44,6 @@ def test_fail_unexpected_parameters(self): ) -class GetNodeAPIConfigurationFilterTestCase(BaseFilterTestCase): - filter_type = \ - GetNodeAPIConfigurationCommand(MockAdapter()).get_response_filter - skip_value_check = True - - # noinspection SpellCheckingInspection - def test_pass_happy_path(self): - """ - The incoming response contains valid values. - """ - response = { - 'maxFindTransactions': 100000, - 'maxRequestsList': 1000, - 'maxGetTrytes': 10000, - 'maxBodyLength': 1000000, - 'testNet': False, - 'milestoneStartIndex': 1050000, - } - - filter_ = self._filter(response) - - self.assertFilterPasses(filter_) - self.assertDictEqual( - filter_.cleaned_data, - { - 'maxFindTransactions': 100000, - 'maxRequestsList': 1000, - 'maxGetTrytes': 10000, - 'maxBodyLength': 1000000, - 'testNet': False, - 'milestoneStartIndex': 1050000, - } - ) - - class GetNodeAPIConfigurationCommandTestCase(TestCase): def setUp(self): super(GetNodeAPIConfigurationCommandTestCase, self).setUp() diff --git a/test/commands/core/get_node_info_test.py b/test/commands/core/get_node_info_test.py index 3c98afa..00a3402 100644 --- a/test/commands/core/get_node_info_test.py +++ b/test/commands/core/get_node_info_test.py @@ -17,7 +17,7 @@ class GetNodeInfoRequestFilterTestCase(BaseFilterTestCase): def test_pass_empty(self): """ - The incoming response is (correctly) empty. + The incoming request is (correctly) empty. """ request = {} @@ -28,7 +28,7 @@ def test_pass_empty(self): def test_fail_unexpected_parameters(self): """ - The incoming response contains unexpected parameters. + The incoming request contains unexpected parameters. """ self.assertFilterErrors( { diff --git a/test/commands/core/get_tips_test.py b/test/commands/core/get_tips_test.py index 0a2f1d1..410335d 100644 --- a/test/commands/core/get_tips_test.py +++ b/test/commands/core/get_tips_test.py @@ -19,7 +19,7 @@ class GetTipsRequestFilterTestCase(BaseFilterTestCase): def test_pass_empty(self): """ - The incoming response is (correctly) empty. + The incoming request is (correctly) empty. """ request = {} @@ -30,7 +30,7 @@ def test_pass_empty(self): def test_fail_unexpected_parameters(self): """ - The incoming response contains unexpected parameters. + The incoming request contains unexpected parameters. """ self.assertFilterErrors( { diff --git a/test/commands/core/were_addresses_spent_from_test.py b/test/commands/core/were_addresses_spent_from_test.py index 65d4cad..4fd482e 100644 --- a/test/commands/core/were_addresses_spent_from_test.py +++ b/test/commands/core/were_addresses_spent_from_test.py @@ -160,46 +160,6 @@ def test_fail_addresses_contents_invalid(self): ) -class WereAddressesSpentFromResponseFilterTestCase(BaseFilterTestCase): - filter_type = \ - WereAddressesSpentFromCommand(MockAdapter()).get_response_filter - skip_value_check = True - - def test_single_address(self): - """ - Typical ``wereAddressesSpentFrom`` response for a single address - """ - filter_ = self._filter({ - 'states': [True, ] - }) - - self.assertFilterPasses(filter_) - self.assertDictEqual( - filter_.cleaned_data, - - { - 'states': [True, ] - }, - ) - - def test_multiple_addresses(self): - """ - Typical ``wereAddressesSpentFrom`` response for a multiple address - """ - filter_ = self._filter({ - 'states': [True, False, False, True] - }) - - self.assertFilterPasses(filter_) - self.assertDictEqual( - filter_.cleaned_data, - - { - 'states': [True, False, False, True] - }, - ) - - class WereAddressesSpentFromCommandTestCase(TestCase): def setUp(self): super(WereAddressesSpentFromCommandTestCase, self).setUp() From a3c2874cb6b3af8fcc5c39671eb14193f0c44260 Mon Sep 17 00:00:00 2001 From: Philipp de Col Date: Sun, 29 Sep 2019 12:17:49 +0200 Subject: [PATCH 7/8] [#235] Fix the response filter for getBalances --- iota/commands/core/get_balances.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/iota/commands/core/get_balances.py b/iota/commands/core/get_balances.py index 97f11c2..f4caef8 100644 --- a/iota/commands/core/get_balances.py +++ b/iota/commands/core/get_balances.py @@ -85,8 +85,7 @@ def __init__(self): 'references': f.Array | f.FilterRepeater( - f.Required | - Trytes(TransactionHash) | - f.Unicode(encoding='ascii', normalize=False), + f.ByteString(encoding='ascii') | + Trytes(TransactionHash) ), }) From 1418ea2fcc57fd7f86e4db1b505fdb79b49a0f29 Mon Sep 17 00:00:00 2001 From: Levente Pap Date: Mon, 30 Sep 2019 13:27:08 +0200 Subject: [PATCH 8/8] Bump version number to 2.0.9 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cf2d869..0dc8553 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ name='PyOTA', description='IOTA API library for Python', url='https://github.com/iotaledger/iota.lib.py', - version='2.0.8', + version='2.0.9', long_description=long_description,