diff --git a/README.rst b/README.rst
index 2cb91ec..eda64bd 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`_.
@@ -61,14 +59,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
@@ -80,14 +78,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::
@@ -98,10 +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/
-.. _detox: https://pypi.python.org/pypi/detox
-.. _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/
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:
...
diff --git a/docs/api.rst b/docs/api.rst
index 1a9c176..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
============
@@ -36,6 +37,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/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 761f50f..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')
@@ -518,6 +562,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/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/get_balances.py b/iota/commands/core/get_balances.py
index 423e9ca..f4caef8 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,47 @@ 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),
+ 'references':
+ f.Array | f.FilterRepeater(
+ f.ByteString(encoding='ascii') |
+ Trytes(TransactionHash)
+ ),
})
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..acaf9a0
--- /dev/null
+++ b/iota/commands/core/get_node_api_configuration.py
@@ -0,0 +1,31 @@
+# coding=utf-8
+from __future__ import absolute_import, division, print_function, \
+ unicode_literals
+
+from iota.commands import FilterCommand, RequestFilter
+
+__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):
+ pass
+
+
+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__({})
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/setup.py b/setup.py
index 4a0aac6..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,
@@ -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/test/commands/core/check_consistency_test.py b/test/commands/core/check_consistency_test.py
index 3b684b5..028f39c 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,234 @@
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 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..4f9ecc8 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..cb6de74
--- /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 GetMissingTransactionsRequestFilterTestCase(BaseFilterTestCase):
+ filter_type = \
+ GetMissingTransactionsCommand(MockAdapter()).get_request_filter
+ skip_value_check = True
+
+ def test_pass_empty(self):
+ """
+ The incoming request 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 request 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..bee9e7a
--- /dev/null
+++ b/test/commands/core/get_node_api_configuration_test.py
@@ -0,0 +1,60 @@
+# 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 request 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 request 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 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_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/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..4fd482e 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,168 @@
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 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,
+ )
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': [],
+ },
+ )
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.