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/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/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, + )