diff --git a/setup.py b/setup.py index 827233e..60f641c 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ name = 'PyOTA', description = 'IOTA API library for Python', url = 'https://github.com/iotaledger/iota.lib.py', - version = '1.0.0b5', + version = '1.0.0b6', packages = find_packages('src'), include_package_data = True, diff --git a/src/iota/adapter.py b/src/iota/adapter/__init__.py similarity index 62% rename from src/iota/adapter.py rename to src/iota/adapter/__init__.py index 8c06b34..b69a2df 100644 --- a/src/iota/adapter.py +++ b/src/iota/adapter/__init__.py @@ -6,13 +6,13 @@ from abc import ABCMeta, abstractmethod as abstract_method from inspect import isabstract as is_abstract from socket import getdefaulttimeout as get_default_timeout -from typing import Dict, Text, Tuple, Union +from typing import Dict, List, Text, Tuple, Union import requests from iota import DEFAULT_PORT from iota.exceptions import with_context from iota.json import JsonEncoder -from six import with_metaclass +from six import PY2, binary_type, with_metaclass __all__ = [ 'AdapterSpec', @@ -20,6 +20,14 @@ 'InvalidUri', ] +if PY2: + # Fix an error when importing this package using the ``imp`` library + # (note: ``imp`` is deprecated since Python 3.4 in favor of + # ``importlib``). + # https://docs.python.org/3/library/imp.html + # https://travis-ci.org/iotaledger/iota.lib.py/jobs/191974244 + __all__ = map(binary_type, __all__) + # Custom types for type hints and docstrings. AdapterSpec = Union[Text, 'BaseAdapter'] @@ -81,7 +89,7 @@ def resolve_adapter(uri): class _AdapterMeta(ABCMeta): """ - Automatically registers new adapter classes in `adapter_registry`. + Automatically registers new adapter classes in ``adapter_registry``. """ # noinspection PyShadowingBuiltins def __init__(cls, what, bases=None, dict=None): @@ -93,7 +101,9 @@ def __init__(cls, what, bases=None, dict=None): def configure(cls, uri): # type: (Text) -> BaseAdapter - """Creates a new adapter from the specified URI.""" + """ + Creates a new adapter from the specified URI. + """ return cls(uri) @@ -102,12 +112,12 @@ class BaseAdapter(with_metaclass(_AdapterMeta)): Interface for IOTA API adapters. Adapters make it easy to customize the way an StrictIota instance - communicates with a node. + communicates with a node. """ supported_protocols = () # type: Tuple[Text] """ - Protocols that `resolve_adapter` can use to identify this adapter - type. + Protocols that ``resolve_adapter`` can use to identify this adapter + type. """ @abstract_method def send_request(self, payload, **kwargs): @@ -115,11 +125,18 @@ def send_request(self, payload, **kwargs): """ Sends an API request to the node. - :param payload: JSON payload. - :param kwargs: Additional keyword arguments for the adapter. + :param payload: + JSON payload. + + :param kwargs: + Additional keyword arguments for the adapter. - :return: Decoded response from the node. - :raise: BadApiResponse if a non-success response was received. + :return: + Decoded response from the node. + + :raise: + - :py:class:`BadApiResponse` if a non-success response was + received. """ raise NotImplementedError( 'Not implemented in {cls}.'.format(cls=type(self).__name__), @@ -138,7 +155,8 @@ def configure(cls, uri): """ Creates a new instance using the specified URI. - :param uri: E.g., `udp://localhost:14265/` + :param uri: + E.g., `udp://localhost:14265/` """ try: protocol, config = uri.split('://', 1) @@ -197,7 +215,9 @@ def __init__(self, host, port=DEFAULT_PORT, path='/'): @property def node_url(self): # type: () -> Text - """Returns the node URL.""" + """ + Returns the node URL. + """ return 'http://{host}:{port}{path}'.format( host = self.host, port = self.port, @@ -241,8 +261,8 @@ def send_request(self, payload, **kwargs): try: # Response always has 200 status, even for errors/exceptions, so the # only way to check for success is to inspect the response body. - # :see:`https://github.com/iotaledger/iri/issues/9` - # :see:`https://github.com/iotaledger/iri/issues/12` + # https://github.com/iotaledger/iri/issues/9 + # https://github.com/iotaledger/iri/issues/12 error = decoded.get('exception') or decoded.get('error') except AttributeError: raise with_context( @@ -268,7 +288,98 @@ def _send_http_request(self, payload, **kwargs): Sends the actual HTTP request. Split into its own method so that it can be mocked during unit - tests. + tests. """ kwargs.setdefault('timeout', get_default_timeout()) return requests.post(self.node_url, data=payload, **kwargs) + + +class MockAdapter(BaseAdapter): + """ + An mock adapter used for simulating API responses. + + To use this adapter, you must first "seed" the responses that the + adapter should return for each request. The adapter will then return + the appropriate seeded response each time it "sends" a request. + """ + supported_protocols = ('mock',) + + # noinspection PyUnusedLocal + @classmethod + def configure(cls, uri): + return cls() + + def __init__(self): + super(MockAdapter, self).__init__() + + self.responses = {} # type: Dict[Text, List[dict]] + self.requests = [] # type: List[dict] + + def seed_response(self, command, response): + # type: (Text, dict) -> MockAdapter + """ + Sets the response that the adapter will return for the specified + command. + + You can seed multiple responses per command; the adapter will put + them into a FIFO queue. When a request comes in, the adapter will + pop the corresponding response off of the queue. + + Example:: + + adapter.seed_response('sayHello', {'message': 'Hi!'}) + adapter.seed_response('sayHello', {'message': 'Hello!'}) + + adapter.send_request({'command': 'sayHello'}) + # {'message': 'Hi!'} + + adapter.send_request({'command': 'sayHello'}) + # {'message': 'Hello!'} + """ + if command not in self.responses: + self.responses[command] = [] + + self.responses[command].append(response) + return self + + def send_request(self, payload, **kwargs): + # type: (dict, dict) -> dict + # Store a snapshot so that we can inspect the request later. + self.requests.append(dict(payload)) + + command = payload['command'] + + try: + response = self.responses[command].pop(0) + except KeyError: + raise with_context( + exc = BadApiResponse( + 'No seeded response for {command!r} ' + '(expected one of: {seeds!r}).'.format( + command = command, + seeds = list(sorted(self.responses.keys())), + ), + ), + + context = { + 'request': payload, + }, + ) + except IndexError: + raise with_context( + exc = BadApiResponse( + '{command} called too many times; no seeded responses left.'.format( + command = command, + ), + ), + + context = { + 'request': payload, + }, + ) + + error = response.get('exception') or response.get('error') + if error: + raise with_context(BadApiResponse(error), context={'request': payload}) + + return response diff --git a/src/iota/adapter/wrappers.py b/src/iota/adapter/wrappers.py new file mode 100644 index 0000000..61aba9e --- /dev/null +++ b/src/iota/adapter/wrappers.py @@ -0,0 +1,70 @@ +# coding=utf-8 +from __future__ import absolute_import, division, print_function, \ + unicode_literals + +from abc import ABCMeta, abstractmethod as abstract_method +from logging import INFO, Logger + +from iota.adapter import AdapterSpec, BaseAdapter, resolve_adapter +from six import with_metaclass + +__all__ = [ + 'LogWrapper', +] + + +class BaseWrapper(with_metaclass(ABCMeta)): + """ + Base functionality for "adapter wrappers", used to extend the + functionality of IOTA adapters. + """ + def __init__(self, adapter): + # type: (AdapterSpec) -> None + super(BaseWrapper, self).__init__() + + if not isinstance(adapter, BaseAdapter): + adapter = resolve_adapter(adapter) + + self.adapter = adapter # type: BaseAdapter + + @abstract_method + def send_request(self, payload, **kwargs): + # type: (dict, dict) -> dict + raise NotImplementedError( + 'Not implemented in {cls}.'.format(cls=type(self).__name__), + ) + + +class LogWrapper(BaseWrapper): + """ + Wrapper that sends all adapter requests and responses to a logger. + + To use it, "wrap" the real adapter instance/spec:: + + logger = getLogger('...') + api = Iota(LogWrapper('http://localhost:14265', logger)) + """ + def __init__(self, adapter, logger, level=INFO): + # type: (AdapterSpec, Logger, int) -> None + super(LogWrapper, self).__init__(adapter) + + self.logger = logger + self.level = level + + def send_request(self, payload, **kwargs): + # type: (dict, dict) -> dict + command = payload.get('command') or 'command' + + self.logger.log(self.level, 'Sending {command}: {request!r}'.format( + command = command, + request = payload, + )) + + response = self.adapter.send_request(payload, **kwargs) + + self.logger.log(self.level, 'Receiving {command}: {response!r}'.format( + command = command, + response = response, + )) + + return response diff --git a/src/iota/api.py b/src/iota/api.py index 1797473..276ecdf 100644 --- a/src/iota/api.py +++ b/src/iota/api.py @@ -489,7 +489,7 @@ def get_new_addresses(self, index=0, count=1): """ return self.getNewAddresses(seed=self.seed, index=index, count=count) - def get_transfers(self, start=0, end=None, inclusion_states=False): + def get_transfers(self, start=0, stop=None, inclusion_states=False): # type: (int, Optional[int], bool) -> dict """ Returns all transfers associated with the seed. @@ -497,14 +497,14 @@ def get_transfers(self, start=0, end=None, inclusion_states=False): :param start: Starting key index. - :param end: + :param stop: Stop before this index. Note that this parameter behaves like the ``stop`` attribute in a - :py:class:`slice` object; the end index is _not_ included in the + :py:class:`slice` object; the stop index is _not_ included in the result. - If not specified, then this method will not stop until it finds - an unused address. + If ``None`` (default), then this method will check every address + until it finds one without any transfers. :param inclusion_states: Whether to also fetch the inclusion states of the transfers. @@ -526,7 +526,7 @@ def get_transfers(self, start=0, end=None, inclusion_states=False): return self.getTransfers( seed = self.seed, start = start, - end = end, + stop = stop, inclusion_states = inclusion_states, ) diff --git a/src/iota/commands/core/attach_to_tangle.py b/src/iota/commands/core/attach_to_tangle.py index 928a6df..8c9968e 100644 --- a/src/iota/commands/core/attach_to_tangle.py +++ b/src/iota/commands/core/attach_to_tangle.py @@ -3,7 +3,7 @@ unicode_literals import filters as f -from iota import TransactionHash +from iota import TransactionHash, TransactionTrytes from iota.commands import FilterCommand, RequestFilter, ResponseFilter from iota.filters import Trytes @@ -33,7 +33,10 @@ def __init__(self): 'branch_transaction': f.Required | Trytes(result_type=TransactionHash), 'trunk_transaction': f.Required | Trytes(result_type=TransactionHash), - 'trytes': f.Required | f.Array | f.FilterRepeater(f.Required | Trytes), + 'trytes': + f.Required + | f.Array + | f.FilterRepeater(f.Required | Trytes(result_type=TransactionTrytes)), # Loosely-validated; testnet nodes require a different value than # mainnet. diff --git a/src/iota/commands/core/broadcast_transactions.py b/src/iota/commands/core/broadcast_transactions.py index 03e7458..b70689e 100644 --- a/src/iota/commands/core/broadcast_transactions.py +++ b/src/iota/commands/core/broadcast_transactions.py @@ -3,7 +3,7 @@ unicode_literals import filters as f - +from iota import TransactionTrytes from iota.commands import FilterCommand, RequestFilter, ResponseFilter from iota.filters import Trytes @@ -30,7 +30,10 @@ def get_response_filter(self): class BroadcastTransactionsRequestFilter(RequestFilter): def __init__(self): super(BroadcastTransactionsRequestFilter, self).__init__({ - 'trytes': f.Required | f.Array | f.FilterRepeater(f.Required | Trytes), + 'trytes': + f.Required + | f.Array + | f.FilterRepeater(f.Required | Trytes(result_type=TransactionTrytes)), }) diff --git a/src/iota/commands/core/get_inclusion_states.py b/src/iota/commands/core/get_inclusion_states.py index 38a7c85..db4e1d9 100644 --- a/src/iota/commands/core/get_inclusion_states.py +++ b/src/iota/commands/core/get_inclusion_states.py @@ -30,16 +30,24 @@ def get_response_filter(self): class GetInclusionStatesRequestFilter(RequestFilter): def __init__(self): - super(GetInclusionStatesRequestFilter, self).__init__({ - 'transactions': ( - f.Required - | f.Array - | f.FilterRepeater(f.Required | Trytes(result_type=TransactionHash)) - ), - - 'tips': ( - f.Required - | f.Array - | f.FilterRepeater(f.Required | Trytes(result_type=TransactionHash)) - ), - }) + super(GetInclusionStatesRequestFilter, self).__init__( + { + # Required parameters. + 'transactions': ( + f.Required + | f.Array + | f.FilterRepeater(f.Required | Trytes(result_type=TransactionHash)) + ), + + # Optional parameters. + 'tips': ( + f.Array + | f.FilterRepeater(f.Required | Trytes(result_type=TransactionHash)) + | f.Optional(default=[]) + ), + }, + + allow_missing_keys = { + 'tips', + }, + ) diff --git a/src/iota/commands/core/store_transactions.py b/src/iota/commands/core/store_transactions.py index e6344a3..32cf0fe 100644 --- a/src/iota/commands/core/store_transactions.py +++ b/src/iota/commands/core/store_transactions.py @@ -3,7 +3,7 @@ unicode_literals import filters as f - +from iota import TransactionTrytes from iota.commands import FilterCommand, RequestFilter from iota.filters import Trytes @@ -30,5 +30,8 @@ def get_response_filter(self): class StoreTransactionsRequestFilter(RequestFilter): def __init__(self): super(StoreTransactionsRequestFilter, self).__init__({ - 'trytes': f.Required | f.Array | f.FilterRepeater(f.Required | Trytes), + 'trytes': + f.Required + | f.Array + | f.FilterRepeater(f.Required | Trytes(result_type=TransactionTrytes)), }) diff --git a/src/iota/commands/extended/get_transfers.py b/src/iota/commands/extended/get_transfers.py index 5ca63a3..48d2999 100644 --- a/src/iota/commands/extended/get_transfers.py +++ b/src/iota/commands/extended/get_transfers.py @@ -36,7 +36,7 @@ def get_response_filter(self): pass def _execute(self, request): - end = request['end'] # type: Optional[int] + stop = request['stop'] # type: Optional[int] inclusion_states = request['inclusion_states'] # type: bool seed = request['seed'] # type: Seed start = request['start'] # type: int @@ -46,7 +46,7 @@ def _execute(self, request): # Determine the addresses we will be scanning, and pull their # transaction hashes. - if end is None: + if stop is None: # This is similar to the ``getNewAddresses`` command, except it # is interested in all the addresses that `getNewAddresses` # skips. @@ -63,55 +63,56 @@ def _execute(self, request): ft_command.reset() else: ft_response =\ - ft_command(addresses=generator.get_addresses(start, end - start)) + ft_command(addresses=generator.get_addresses(start, stop - start)) hashes = ft_response.get('hashes') or [] - # Sort transactions into tail and non-tail. - tails = set() - non_tails = set() - - gt_response = GetTrytesCommand(self.adapter)(hashes=hashes) - transactions = list(map( - Transaction.from_tryte_string, - gt_response['trytes'], - )) - - for txn in transactions: - if txn.is_tail: - tails.add(txn.hash) - else: - # Capture the bundle ID instead of the transaction hash so that - # we can query the node to find the tail transaction for that - # bundle. - non_tails.add(txn.bundle_hash) - - if non_tails: - for txn in self._find_transactions(bundles=non_tails): + all_bundles = [] # type: List[Bundle] + + if hashes: + # Sort transactions into tail and non-tail. + tails = set() + non_tails = set() + + gt_response = GetTrytesCommand(self.adapter)(hashes=hashes) + transactions = list(map( + Transaction.from_tryte_string, + gt_response['trytes'], + )) + + for txn in transactions: if txn.is_tail: tails.add(txn.hash) + else: + # Capture the bundle ID instead of the transaction hash so that + # we can query the node to find the tail transaction for that + # bundle. + non_tails.add(txn.bundle_hash) - # Attach inclusion states, if requested. - if inclusion_states: - gli_response = GetLatestInclusionCommand(self.adapter)( - hashes = tails, - ) + if non_tails: + for txn in self._find_transactions(bundles=non_tails): + if txn.is_tail: + tails.add(txn.hash) - for txn in transactions: - txn.is_confirmed = gli_response['states'].get(txn.hash) + # Attach inclusion states, if requested. + if inclusion_states: + gli_response = GetLatestInclusionCommand(self.adapter)( + hashes = list(tails), + ) - all_bundles = [] # type: List[Bundle] + for txn in transactions: + txn.is_confirmed = gli_response['states'].get(txn.hash) - # Find the bundles for each transaction. - for txn in transactions: - gb_response = GetBundlesCommand(self.adapter)(transaction=txn.hash) - txn_bundles = gb_response['bundles'] # type: List[Bundle] + # Find the bundles for each transaction. + for txn in transactions: + gb_response = GetBundlesCommand(self.adapter)(transaction=txn.hash) + txn_bundles = gb_response['bundles'] # type: List[Bundle] - if inclusion_states: - for bundle in txn_bundles: - bundle.is_confirmed = txn.is_confirmed + if inclusion_states: + for bundle in txn_bundles: + bundle.is_confirmed = txn.is_confirmed - all_bundles.extend(txn_bundles) + all_bundles.extend(txn_bundles) return { # Sort bundles by tail transaction timestamp. @@ -150,8 +151,8 @@ class GetTransfersRequestFilter(RequestFilter): CODE_INTERVAL_TOO_BIG = 'interval_too_big' templates = { - CODE_INTERVAL_INVALID: '``start`` must be <= ``end``', - CODE_INTERVAL_TOO_BIG: '``end`` - ``start`` must be <= {max_interval}', + CODE_INTERVAL_INVALID: '``start`` must be <= ``stop``', + CODE_INTERVAL_TOO_BIG: '``stop`` - ``start`` must be <= {max_interval}', } def __init__(self): @@ -161,14 +162,14 @@ def __init__(self): 'seed': f.Required | Trytes(result_type=Seed), # Optional parameters. - 'end': f.Type(int) | f.Min(0), + 'stop': f.Type(int) | f.Min(0), 'start': f.Type(int) | f.Min(0) | f.Optional(0), 'inclusion_states': f.Type(bool) | f.Optional(False), }, allow_missing_keys = { - 'end', + 'stop', 'inclusion_states', 'start', }, @@ -181,8 +182,8 @@ def _apply(self, value): if self._has_errors: return filtered - if filtered['end'] is not None: - if filtered['start'] > filtered['end']: + if filtered['stop'] is not None: + if filtered['start'] > filtered['stop']: filtered['start'] = self._invalid_value( value = filtered['start'], reason = self.CODE_INTERVAL_INVALID, @@ -190,18 +191,18 @@ def _apply(self, value): context = { 'start': filtered['start'], - 'end': filtered['end'], + 'stop': filtered['stop'], }, ) - elif (filtered['end'] - filtered['start']) > self.MAX_INTERVAL: - filtered['end'] = self._invalid_value( - value = filtered['end'], + elif (filtered['stop'] - filtered['start']) > self.MAX_INTERVAL: + filtered['stop'] = self._invalid_value( + value = filtered['stop'], reason = self.CODE_INTERVAL_TOO_BIG, - sub_key = 'end', + sub_key = 'stop', context = { 'start': filtered['start'], - 'end': filtered['end'], + 'stop': filtered['stop'], }, template_vars = { diff --git a/src/iota/commands/extended/replay_bundle.py b/src/iota/commands/extended/replay_bundle.py index 091f685..f29a8ed 100644 --- a/src/iota/commands/extended/replay_bundle.py +++ b/src/iota/commands/extended/replay_bundle.py @@ -36,15 +36,19 @@ def _execute(self, request): min_weight_magnitude = request['min_weight_magnitude'] # type: int transaction = request['transaction'] # type: TransactionHash - bundles = GetBundlesCommand(self.adapter)(transaction=transaction) # type: List[Bundle] + gb_response = GetBundlesCommand(self.adapter)(transaction=transaction) + + # Note that we only replay the first bundle returned by + # ``getBundles``. + bundle = gb_response['bundles'][0] # type: Bundle return SendTrytesCommand(self.adapter)( depth = depth, min_weight_magnitude = min_weight_magnitude, - trytes = list(reversed(b.as_tryte_string() for b in bundles)), - ) + trytes = bundle.as_tryte_strings(head_to_tail=True), + ) class ReplayBundleRequestFilter(RequestFilter): diff --git a/src/iota/commands/extended/send_transfer.py b/src/iota/commands/extended/send_transfer.py index 27a121a..15a7ca4 100644 --- a/src/iota/commands/extended/send_transfer.py +++ b/src/iota/commands/extended/send_transfer.py @@ -39,20 +39,22 @@ def _execute(self, request): seed = request['seed'] # type: Seed transfers = request['transfers'] # type: List[ProposedTransaction] - prepared_trytes = PrepareTransferCommand(self.adapter)( + pt_response = PrepareTransferCommand(self.adapter)( change_address = change_address, inputs = inputs, seed = seed, transfers = transfers, ) - sent_trytes = SendTrytesCommand(self.adapter)( + st_response = SendTrytesCommand(self.adapter)( depth = depth, min_weight_magnitude = min_weight_magnitude, - trytes = prepared_trytes, + trytes = pt_response['trytes'], ) - return Bundle.from_tryte_strings(sent_trytes) + return { + 'bundle': Bundle.from_tryte_strings(st_response['trytes']), + } class SendTransferRequestFilter(RequestFilter): diff --git a/src/iota/commands/extended/send_trytes.py b/src/iota/commands/extended/send_trytes.py index 049d7d9..0c709ba 100644 --- a/src/iota/commands/extended/send_trytes.py +++ b/src/iota/commands/extended/send_trytes.py @@ -5,7 +5,7 @@ from typing import List import filters as f -from iota import TryteString +from iota import TransactionTrytes, TryteString from iota.commands import FilterCommand, RequestFilter from iota.commands.core.attach_to_tangle import AttachToTangleCommand from iota.commands.core.get_transactions_to_approve import \ @@ -57,7 +57,10 @@ def __init__(self): super(SendTrytesRequestFilter, self).__init__({ 'depth': f.Required | f.Type(int) | f.Min(1), - 'trytes': f.Required | f.Array | f.FilterRepeater(f.Required | Trytes), + 'trytes': + f.Required + | f.Array + | f.FilterRepeater(f.Required | Trytes(result_type=TransactionTrytes)), # Loosely-validated; testnet nodes require a different value than # mainnet. diff --git a/src/iota/filters.py b/src/iota/filters.py index 9b07383..274bf61 100644 --- a/src/iota/filters.py +++ b/src/iota/filters.py @@ -2,13 +2,12 @@ from __future__ import absolute_import, division, print_function, \ unicode_literals -from typing import Text, Union +from typing import Text import filters as f -from six import binary_type, text_type - from iota import Address, TryteString, TrytesCompatible from iota.adapter import resolve_adapter, InvalidUri +from six import binary_type, text_type class GeneratedAddress(f.BaseFilter): @@ -129,5 +128,5 @@ def _apply(self, value): template_vars = { 'result_type': self.result_type.__name__, - } + }, ) diff --git a/src/iota/transaction.py b/src/iota/transaction.py index bddb5b6..9ef0229 100644 --- a/src/iota/transaction.py +++ b/src/iota/transaction.py @@ -4,6 +4,7 @@ from calendar import timegm as unix_timestamp from datetime import datetime +from operator import attrgetter from typing import Generator, Iterable, Iterator, List, MutableSequence, \ Optional, Sequence, Text, Tuple @@ -25,6 +26,7 @@ 'ProposedTransaction', 'Transaction', 'TransactionHash', + 'TransactionTrytes', ] def get_current_timestamp(): @@ -80,6 +82,29 @@ def __init__(self, trytes): ) +class TransactionTrytes(TryteString): + """ + A TryteString representation of a Transaction. + """ + LEN = 2673 + + def __init__(self, trytes): + # type: (TrytesCompatible) -> None + super(TransactionTrytes, self).__init__(trytes, pad=self.LEN) + + if len(self._trytes) > self.LEN: + raise with_context( + exc = ValueError('{cls} values must be {len} trytes long.'.format( + cls = type(self).__name__, + len = self.LEN + )), + + context = { + 'trytes': trytes, + }, + ) + + class Transaction(JsonSerializable): """ A transaction that has been attached to the Tangle. @@ -90,7 +115,7 @@ def from_tryte_string(cls, trytes): """ Creates a Transaction object from a sequence of trytes. """ - tryte_string = TryteString(trytes) + tryte_string = TransactionTrytes(trytes) hash_ = [0] * HASH_LENGTH # type: MutableSequence[int] @@ -302,11 +327,11 @@ def as_json_compatible(self): } def as_tryte_string(self): - # type: () -> TryteString + # type: () -> TransactionTrytes """ Returns a TryteString representation of the transaction. """ - return ( + return TransactionTrytes( self.signature_message_fragment + self.address.address + self.value_as_trytes @@ -419,7 +444,11 @@ def __init__(self, transactions=None): # type: (Optional[Iterable[Transaction]]) -> None super(Bundle, self).__init__() - self.transactions = transactions or [] # type: List[Transaction] + self.transactions = [] # type: List[Transaction] + if transactions: + self.transactions.extend( + sorted(transactions, key=attrgetter('current_index')) + ) self._is_confirmed = None # type: Optional[bool] """ @@ -494,6 +523,21 @@ def tail_transaction(self): """ return self[0] + def as_tryte_strings(self, head_to_tail=True): + # type: (bool) -> List[TransactionTrytes] + """ + Returns TryteString representations of the transactions in this + bundle. + + :param head_to_tail: + Determines the order of the transactions: + + - ``True`` (default): head txn first, tail txn last. + - ``False``: tail txn first, head txn last. + """ + transactions = reversed(self) if head_to_tail else self + return [t.as_tryte_string() for t in transactions] + def as_json_compatible(self): # type: () -> List[dict] """ diff --git a/test/__init__.py b/test/__init__.py index 7bce6aa..3f3d02d 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,97 +1,3 @@ # coding=utf-8 from __future__ import absolute_import, division, print_function, \ unicode_literals - -from typing import Dict, List, Optional, Text - -from iota import BadApiResponse -from iota.adapter import BaseAdapter -from iota.exceptions import with_context - - -class MockAdapter(BaseAdapter): - """ - An adapter for IotaApi that always returns a mocked response. - """ - supported_protocols = ('mock',) - - # noinspection PyUnusedLocal - @classmethod - def configure(cls, uri): - return cls() - - def __init__(self): - # type: (Optional[dict]) -> None - super(MockAdapter, self).__init__() - - self.responses = {} # type: Dict[Text, List[dict]] - self.requests = [] # type: List[dict] - - def seed_response(self, command, response): - # type: (Text, dict) -> MockAdapter - """ - Sets the response that the adapter will return for the specified - command. - - You can seed multiple responses per command; the adapter will put - them into a FIFO queue. When a request comes in, the adapter will - pop the corresponding response off of the queue. - - Example:: - - adapter.seed_response('sayHello', {'message': 'Hi!'}) - adapter.seed_response('sayHello', {'message': 'Hello!'}) - - adapter.send_request({'command': 'sayHello'}) - # {'message': 'Hi!'} - - adapter.send_request({'command': 'sayHello'}) - # {'message': 'Hello!'} - """ - if command not in self.responses: - self.responses[command] = [] - - self.responses[command].append(response) - return self - - def send_request(self, payload, **kwargs): - # type: (dict, dict) -> dict - # Store a snapshot so that we can inspect the request later. - self.requests.append(dict(payload)) - - command = payload['command'] - - try: - response = self.responses[command].pop(0) - except KeyError: - raise with_context( - exc = BadApiResponse( - 'No seeded response for {command!r} ' - '(expected one of: {seeds!r}).'.format( - command = command, - seeds = list(sorted(self.responses.keys())), - ), - ), - - context = { - 'request': payload, - }, - ) - except IndexError: - raise with_context( - exc = BadApiResponse( - '{command} called too many times; no seeded responses left.'.format( - command = command, - ), - ), - - context = { - 'request': payload, - }, - ) - - error = response.get('exception') or response.get('error') - if error: - raise with_context(BadApiResponse(error), context={'request': payload}) - - return response diff --git a/test/adapter_test.py b/test/adapter_test.py index 7430b47..4389005 100644 --- a/test/adapter_test.py +++ b/test/adapter_test.py @@ -11,13 +11,12 @@ from six import BytesIO, text_type as text from iota import BadApiResponse, DEFAULT_PORT, InvalidUri, TryteString -from iota.adapter import HttpAdapter, resolve_adapter -from test import MockAdapter +from iota.adapter import HttpAdapter, MockAdapter, resolve_adapter class ResolveAdapterTestCase(TestCase): """ - Unit tests for the `resolve_adapter` function. + Unit tests for :py:func:`resolve_adapter`. """ def test_adapter_instance(self): """ @@ -342,11 +341,11 @@ def _create_response(content): """ Creates a Response object for a test. """ - # :see: requests.adapters.HTTPAdapter.build_response + # :py:meth:`requests.adapters.HTTPAdapter.build_response` response = requests.Response() # Response status is always 200, even for an error. - # :see: https://github.com/iotaledger/iri/issues/9 + # https://github.com/iotaledger/iri/issues/9 response.status_code = 200 response.encoding = 'utf-8' diff --git a/test/api_test.py b/test/api_test.py index 0475a5a..5b10d85 100644 --- a/test/api_test.py +++ b/test/api_test.py @@ -5,9 +5,9 @@ from unittest import TestCase from iota import StrictIota +from iota.adapter import MockAdapter from iota.commands import CustomCommand from iota.commands.core.get_node_info import GetNodeInfoCommand -from test import MockAdapter class CustomCommandTestCase(TestCase): @@ -19,7 +19,9 @@ def setUp(self): self.command = CustomCommand(self.adapter, self.name) def test_call(self): - """Sending a custom command.""" + """ + Sending a custom command. + """ expected_response = {'message': 'Hello, IOTA!'} self.adapter.seed_response('helloWorld', expected_response) @@ -35,7 +37,9 @@ def test_call(self): ) def test_call_with_parameters(self): - """Sending a custom command with parameters.""" + """ + Sending a custom command with parameters. + """ expected_response = {'message': 'Hello, IOTA!'} self.adapter.seed_response('helloWorld', expected_response) @@ -51,7 +55,9 @@ def test_call_with_parameters(self): ) def test_call_error_already_called(self): - """A command can only be called once.""" + """ + A command can only be called once. + """ self.adapter.seed_response('helloWorld', {}) self.command() @@ -61,7 +67,9 @@ def test_call_error_already_called(self): self.assertDictEqual(self.command.request, {'command': 'helloWorld'}) def test_call_reset(self): - """Resetting a command allows it to be called more than once.""" + """ + Resetting a command allows it to be called more than once. + """ self.adapter.seed_response('helloWorld', {'message': 'Hello, IOTA!'}) self.command() @@ -97,19 +105,23 @@ def test_init_with_uri(self): self.assertIsInstance(api.adapter, MockAdapter) def test_registered_command(self): - """Preparing a documented command.""" + """ + Preparing a documented command. + """ api = StrictIota(MockAdapter()) # We just need to make sure the correct command type is - # instantiated; individual commands have their own unit tests. + # instantiated; individual commands have their own unit tests. command = api.getNodeInfo self.assertIsInstance(command, GetNodeInfoCommand) def test_custom_command(self): - """Preparing an experimental/undocumented command.""" + """ + Preparing an experimental/undocumented command. + """ api = StrictIota(MockAdapter()) # We just need to make sure the correct command type is - # instantiated; custom commands have their own unit tests. + # instantiated; custom commands have their own unit tests. command = api.helloWorld self.assertIsInstance(command, CustomCommand) diff --git a/test/commands/core/add_neighbors_test.py b/test/commands/core/add_neighbors_test.py index f59646f..8d08085 100644 --- a/test/commands/core/add_neighbors_test.py +++ b/test/commands/core/add_neighbors_test.py @@ -7,9 +7,9 @@ import filters as f from filters.test import BaseFilterTestCase from iota import Iota +from iota.adapter import MockAdapter from iota.commands.core.add_neighbors import AddNeighborsCommand from iota.filters import NodeUri -from test import MockAdapter class AddNeighborsRequestFilterTestCase(BaseFilterTestCase): @@ -17,7 +17,9 @@ class AddNeighborsRequestFilterTestCase(BaseFilterTestCase): skip_value_check = True def test_pass_valid_request(self): - """The incoming request is valid.""" + """ + The incoming request is valid. + """ request = { 'uris': [ 'udp://node1.iotatoken.com', @@ -31,7 +33,9 @@ def test_pass_valid_request(self): self.assertDictEqual(filter_.cleaned_data, request) def test_fail_empty(self): - """The incoming request is empty.""" + """ + The incoming request is empty. + """ self.assertFilterErrors( {}, @@ -41,7 +45,9 @@ def test_fail_empty(self): ) def test_fail_unexpected_parameters(self): - """The incoming request contains unexpected parameters.""" + """ + The incoming request contains unexpected parameters. + """ self.assertFilterErrors( { 'uris': ['udp://localhost'], @@ -56,7 +62,9 @@ def test_fail_unexpected_parameters(self): ) def test_fail_neighbors_null(self): - """`uris` is null.""" + """ + ``uris`` is null. + """ self.assertFilterErrors( { 'uris': None, @@ -68,11 +76,13 @@ def test_fail_neighbors_null(self): ) def test_fail_uris_wrong_type(self): - """`uris` is not an array.""" + """ + ``uris`` is not an array. + """ self.assertFilterErrors( { # Nope; it's gotta be an array, even if you only want to add - # a single neighbor. + # a single neighbor. 'uris': 'http://localhost:8080/' }, @@ -82,7 +92,9 @@ def test_fail_uris_wrong_type(self): ) def test_fail_uris_empty(self): - """`uris` is an array, but it's empty.""" + """ + ``uris`` is an array, but it's empty. + """ self.assertFilterErrors( { # Insert "Forever Alone" meme here. @@ -96,12 +108,10 @@ def test_fail_uris_empty(self): def test_fail_uris_contents_invalid(self): """ - `uris` is an array, but it contains invalid values. + ``uris`` is an array, but it contains invalid values. """ self.assertFilterErrors( { - # When I said it has to be an array before, I meant an array of - # strings! 'uris': [ '', False, @@ -110,7 +120,7 @@ def test_fail_uris_contents_invalid(self): 'not a valid uri', # This is actually valid; I just added it to make sure the - # filter isn't cheating! + # filter isn't cheating! 'udp://localhost', 2130706433, diff --git a/test/commands/core/attach_to_tangle_test.py b/test/commands/core/attach_to_tangle_test.py index bdd83d2..1003be7 100644 --- a/test/commands/core/attach_to_tangle_test.py +++ b/test/commands/core/attach_to_tangle_test.py @@ -6,11 +6,11 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Iota, TransactionHash, TryteString +from iota import Iota, TransactionHash, TransactionTrytes, TryteString +from iota.adapter import MockAdapter from iota.commands.core.attach_to_tangle import AttachToTangleCommand from iota.filters import Trytes from six import binary_type, text_type -from test import MockAdapter class AttachToTangleRequestFilterTestCase(BaseFilterTestCase): @@ -22,7 +22,7 @@ def setUp(self): super(AttachToTangleRequestFilterTestCase, self).setUp() # Define a few valid values here that we can reuse across multiple - # tests. + # tests. self.txn_id = ( b'JVMTDGDPDFYHMZPMWEKKANBQSLSDTIIHAYQUMZOK' b'HXXXGJHJDQPOMDOMNRDKYCZRUFZROZDADTHZC9999' @@ -42,8 +42,8 @@ def test_pass_happy_path(self): 'min_weight_magnitude': 20, 'trytes': [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ], } @@ -64,8 +64,8 @@ def test_pass_compatible_types(self): 'branch_transaction': bytearray(self.txn_id), 'trytes': [ - # `trytes` can contain any value that can be converted into a - # TryteString. + # ``trytes`` can contain any value that can be converted into a + # TryteString. binary_type(self.trytes1), # This is probably wrong, but technically it's valid. @@ -83,16 +83,16 @@ def test_pass_compatible_types(self): filter_.cleaned_data, # After running through the filter, all of the values have been - # converted to the correct types. + # converted to the correct types. { 'trunk_transaction': TransactionHash(self.txn_id), 'branch_transaction': TransactionHash(self.txn_id), 'min_weight_magnitude': 30, 'trytes': [ - TryteString(self.trytes1), + TransactionTrytes(self.trytes1), - TryteString( + TransactionTrytes( b'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHD' b'WCTCEAKDCDFD9DSCSA99999999999999999999999', ), @@ -116,7 +116,9 @@ def test_fail_empty(self): ) def test_fail_unexpected_parameters(self): - """The incoming request contains unexpected parameters.""" + """ + The incoming request contains unexpected parameters. + """ self.assertFilterErrors( { 'branch_transaction': TransactionHash(self.txn_id), @@ -357,6 +359,8 @@ def test_fail_trytes_contents_invalid(self): TryteString(self.trytes2), 2130706433, + + b'9' * (TransactionTrytes.LEN + 1), ], 'branch_transaction': TransactionHash(self.txn_id), @@ -371,6 +375,7 @@ def test_fail_trytes_contents_invalid(self): 'trytes.3': [f.Required.CODE_EMPTY], 'trytes.4': [Trytes.CODE_NOT_TRYTES], 'trytes.6': [f.Type.CODE_WRONG_TYPE], + 'trytes.7': [Trytes.CODE_WRONG_FORMAT], }, ) @@ -407,7 +412,6 @@ def test_pass_happy_path(self): filter_.cleaned_data, { - # The filter converts them into TryteStrings. 'trytes': [ TryteString(self.trytes1), TryteString(self.trytes2), diff --git a/test/commands/core/broadcast_transactions_test.py b/test/commands/core/broadcast_transactions_test.py index 6e1a0b7..e53d351 100644 --- a/test/commands/core/broadcast_transactions_test.py +++ b/test/commands/core/broadcast_transactions_test.py @@ -6,12 +6,12 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Iota, TryteString +from iota import Iota, TransactionTrytes, TryteString +from iota.adapter import MockAdapter from iota.commands.core.broadcast_transactions import \ BroadcastTransactionsCommand from iota.filters import Trytes from six import binary_type, text_type -from test import MockAdapter class BroadcastTransactionsRequestFilterTestCase(BaseFilterTestCase): @@ -28,11 +28,13 @@ def setUp(self): b'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHDWCTCEAKDCDFD9DSCSA' def test_pass_happy_path(self): - """The incoming request is valid.""" + """ + The incoming request is valid. + """ request = { 'trytes': [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ], } @@ -44,7 +46,7 @@ def test_pass_happy_path(self): def test_pass_compatible_types(self): """ The incoming request contains values that can be converted into the - expected types. + expected types. """ # Any values that can be converted into TryteStrings are accepted. filter_ = self._filter({ @@ -59,17 +61,19 @@ def test_pass_compatible_types(self): filter_.cleaned_data, # The values are converted into TryteStrings so that they can be - # sent to the node. + # sent to the node. { 'trytes': [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ], }, ) def test_fail_empty(self): - """The incoming request is empty.""" + """ + The incoming request is empty. + """ self.assertFilterErrors( {}, @@ -79,7 +83,9 @@ def test_fail_empty(self): ) def test_fail_unexpected_parameters(self): - """The incoming value contains unexpected parameters.""" + """ + The incoming value contains unexpected parameters. + """ self.assertFilterErrors( { 'trytes': [TryteString(self.trytes1)], @@ -94,7 +100,9 @@ def test_fail_unexpected_parameters(self): ) def test_fail_trytes_null(self): - """`trytes` is null.""" + """ + ``trytes`` is null. + """ self.assertFilterErrors( { 'trytes': None, @@ -106,11 +114,13 @@ def test_fail_trytes_null(self): ) def test_fail_trytes_wrong_type(self): - """`trytes` is not an array.""" + """ + ``trytes`` is not an array. + """ self.assertFilterErrors( { - # `trytes` has to be an array, even if there's only one - # TryteString. + # ``trytes`` has to be an array, even if there's only one + # TryteString. 'trytes': TryteString(self.trytes1), }, @@ -120,7 +130,9 @@ def test_fail_trytes_wrong_type(self): ) def test_fail_trytes_empty(self): - """`trytes` is an array, but it's empty.""" + """ + ``trytes`` is an array, but it's empty. + """ self.assertFilterErrors( { 'trytes': [], @@ -132,7 +144,9 @@ def test_fail_trytes_empty(self): ) def test_trytes_contents_invalid(self): - """`trytes` is an array, but it contains invalid values.""" + """ + ``trytes`` is an array, but it contains invalid values. + """ self.assertFilterErrors( { 'trytes': [ @@ -143,10 +157,12 @@ def test_trytes_contents_invalid(self): b'not valid trytes', # This is actually valid; I just added it to make sure the - # filter isn't cheating! + # filter isn't cheating! TryteString(self.trytes2), 2130706433, + + b'9' * (TransactionTrytes.LEN + 1), ], }, @@ -157,6 +173,7 @@ def test_trytes_contents_invalid(self): 'trytes.3': [f.Required.CODE_EMPTY], 'trytes.4': [Trytes.CODE_NOT_TRYTES], 'trytes.6': [f.Type.CODE_WRONG_TYPE], + 'trytes.7': [Trytes.CODE_WRONG_FORMAT], }, ) @@ -170,13 +187,15 @@ def setUp(self): super(BroadcastTransactionsResponseFilterTestCase, self).setUp() # Define a few valid values here that we can reuse across multiple - # tests. + # tests. self.trytes1 = b'RBTC9D9DCDQAEASBYBCCKBFA' self.trytes2 =\ b'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHDWCTCEAKDCDFD9DSCSA' def test_pass_happy_path(self): - """The incoming response contains valid values.""" + """ + The incoming response contains valid values. + """ # Responses from the node arrive as strings. filter_ = self._filter({ 'trytes': [ @@ -187,7 +206,6 @@ def test_pass_happy_path(self): self.assertFilterPasses(filter_) - # The filter converts them into TryteStrings. self.assertDictEqual( filter_.cleaned_data, diff --git a/test/commands/core/find_transactions_test.py b/test/commands/core/find_transactions_test.py index 2402ff3..22f3ac4 100644 --- a/test/commands/core/find_transactions_test.py +++ b/test/commands/core/find_transactions_test.py @@ -7,11 +7,11 @@ import filters as f from filters.test import BaseFilterTestCase from iota import Address, Iota, Tag, TransactionHash, TryteString +from iota.adapter import MockAdapter from iota.commands.core.find_transactions import FindTransactionsCommand, \ FindTransactionsRequestFilter from iota.filters import Trytes from six import binary_type, text_type -from test import MockAdapter class FindTransactionsRequestFilterTestCase(BaseFilterTestCase): @@ -29,7 +29,9 @@ def setUp(self): self.trytes3 = b'999999999999999999999999999' def test_pass_all_parameters(self): - """The request contains valid values for all parameters.""" + """ + The request contains valid values for all parameters. + """ request = { 'bundles': [ TransactionHash(self.trytes1), @@ -112,7 +114,9 @@ def test_pass_compatible_types(self): ) def test_pass_bundles_only(self): - """The request only includes bundles.""" + """ + The request only includes bundles. + """ request = { 'bundles': [ TransactionHash(self.trytes1), @@ -139,7 +143,9 @@ def test_pass_bundles_only(self): ) def test_pass_addresses_only(self): - """The request only includes addresses.""" + """ + The request only includes addresses. + """ request = { 'addresses': [ Address(self.trytes1), @@ -166,7 +172,9 @@ def test_pass_addresses_only(self): ) def test_pass_tags_only(self): - """The request only includes tags.""" + """ + The request only includes tags. + """ request = { 'tags': [ Tag(self.trytes1), @@ -193,7 +201,9 @@ def test_pass_tags_only(self): ) def test_pass_approvees_only(self): - """The request only includes approvees.""" + """ + The request only includes approvees. + """ request = { 'approvees': [ TransactionHash(self.trytes1), @@ -220,7 +230,9 @@ def test_pass_approvees_only(self): ) def test_fail_empty(self): - """The request does not contain any parameters.""" + """ + The request does not contain any parameters. + """ self.assertFilterErrors( {}, @@ -230,7 +242,9 @@ def test_fail_empty(self): ) def test_fail_all_parameters_empty(self): - """The request contains all parameters, but every one is empty.""" + """ + The request contains all parameters, but every one is empty. + """ self.assertFilterErrors( { 'addresses': [], @@ -245,7 +259,9 @@ def test_fail_all_parameters_empty(self): ) def test_fail_unexpected_parameters(self): - """The request contains unexpected parameters.""" + """ + The request contains unexpected parameters. + """ self.assertFilterErrors( { 'addresses': [Address(self.trytes1)], @@ -263,7 +279,9 @@ def test_fail_unexpected_parameters(self): ) def test_fail_bundles_wrong_type(self): - """`bundles` is not an array.""" + """ + ``bundles`` is not an array. + """ self.assertFilterErrors( { 'bundles': TransactionHash(self.trytes1), @@ -275,7 +293,9 @@ def test_fail_bundles_wrong_type(self): ) def test_fail_bundles_contents_invalid(self): - """`bundles` is an array, but it contains invalid values.""" + """ + ``bundles`` is an array, but it contains invalid values. + """ self.assertFilterErrors( { 'bundles': [ @@ -286,7 +306,7 @@ def test_fail_bundles_contents_invalid(self): b'not valid trytes', # This is actually valid; I just added it to make sure the - # filter isn't cheating! + # filter isn't cheating! TryteString(self.trytes2), 2130706433, @@ -306,7 +326,9 @@ def test_fail_bundles_contents_invalid(self): ) def test_fail_addresses_wrong_type(self): - """`addresses` is not an array.""" + """ + ``addresses`` is not an array. + """ self.assertFilterErrors( { 'addresses': Address(self.trytes1), @@ -318,7 +340,9 @@ def test_fail_addresses_wrong_type(self): ) def test_fail_addresses_contents_invalid(self): - """`addresses` is an array, but it contains invalid values.""" + """ + ``addresses`` is an array, but it contains invalid values. + """ self.assertFilterErrors( { 'addresses': [ @@ -329,7 +353,7 @@ def test_fail_addresses_contents_invalid(self): b'not valid trytes', # This is actually valid; I just added it to make sure the - # filter isn't cheating! + # filter isn't cheating! TryteString(self.trytes2), 2130706433, @@ -349,7 +373,9 @@ def test_fail_addresses_contents_invalid(self): ) def test_fail_tags_wrong_type(self): - """`tags` is not an array.""" + """ + ``tags`` is not an array. + """ self.assertFilterErrors( { 'tags': Tag(self.trytes1), @@ -361,7 +387,9 @@ def test_fail_tags_wrong_type(self): ) def test_fail_tags_contents_invalid(self): - """`tags` is an array, but it contains invalid values.""" + """ + ``tags`` is an array, but it contains invalid values. + """ self.assertFilterErrors( { 'tags': [ @@ -372,7 +400,7 @@ def test_fail_tags_contents_invalid(self): b'not valid trytes', # This is actually valid; I just added it to make sure the - # filter isn't cheating! + # filter isn't cheating! TryteString(self.trytes1), 2130706433, @@ -392,7 +420,9 @@ def test_fail_tags_contents_invalid(self): ) def test_fail_approvees_wrong_type(self): - """`approvees` is not an array.""" + """ + ``approvees`` is not an array. + """ self.assertFilterErrors( { 'approvees': TransactionHash(self.trytes1), @@ -404,7 +434,9 @@ def test_fail_approvees_wrong_type(self): ) def test_fail_approvees_contents_invalid(self): - """`approvees` is an array, but it contains invalid values.""" + """ + ``approvees`` is an array, but it contains invalid values. + """ self.assertFilterErrors( { 'approvees': [ @@ -415,7 +447,7 @@ def test_fail_approvees_contents_invalid(self): b'not valid trytes', # This is actually valid; I just added it to make sure the - # filter isn't cheating! + # filter isn't cheating! TryteString(self.trytes2), 2130706433, @@ -444,13 +476,15 @@ def setUp(self): super(FindTransactionsResponseFilterTestCase, self).setUp() # Define a few valid values here that we can reuse across multiple - # tests. + # tests. self.trytes1 = b'RBTC9D9DCDQAEASBYBCCKBFA' self.trytes2 =\ b'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHDWCTCEAKDCDFD9DSCSA' def test_no_results(self): - """The incoming response contains no hashes.""" + """ + The incoming response contains no hashes. + """ response = { 'hashes': [], 'duration': 42, @@ -463,7 +497,9 @@ def test_no_results(self): # noinspection SpellCheckingInspection def test_search_results(self): - """The incoming response contains lots of hashes.""" + """ + The incoming response contains lots of hashes. + """ filter_ = self._filter({ 'hashes': [ 'RVORZ9SIIP9RCYMREUIXXVPQIPHVCNPQ9HZWYKFW' diff --git a/test/commands/core/get_balances_test.py b/test/commands/core/get_balances_test.py index b30c545..89f4b4b 100644 --- a/test/commands/core/get_balances_test.py +++ b/test/commands/core/get_balances_test.py @@ -7,10 +7,10 @@ import filters as f from filters.test import BaseFilterTestCase from iota import Address, Iota, TryteString +from iota.adapter import MockAdapter from iota.commands.core.get_balances import GetBalancesCommand from iota.filters import Trytes from six import binary_type, text_type -from test import MockAdapter class GetBalancesRequestFilterTestCase(BaseFilterTestCase): @@ -33,7 +33,9 @@ def setUp(self): ) def test_pass_happy_path(self): - """Typical invocation of `getBalances`.""" + """ + Typical invocation of ``getBalances``. + """ request = { 'addresses': [ Address(self.trytes1), @@ -80,7 +82,7 @@ def test_pass_compatible_types(self): def test_pass_threshold_optional(self): """ - The incoming request does not contain a `threshold` value, so the + The incoming request does not contain a ``threshold`` value, so the default value is assumed. """ request = { @@ -100,7 +102,9 @@ def test_pass_threshold_optional(self): ) def test_fail_empty(self): - """The incoming request is empty.""" + """ + The incoming request is empty. + """ self.assertFilterErrors( {}, @@ -110,7 +114,9 @@ def test_fail_empty(self): ) def test_fail_unexpected_parameters(self): - """The incoming request contains unexpected parameters.""" + """ + The incoming request contains unexpected parameters. + """ self.assertFilterErrors( { 'addresses': [Address(self.trytes1)], @@ -126,7 +132,9 @@ def test_fail_unexpected_parameters(self): ) def test_fail_addresses_wrong_type(self): - """`addresses` is not an array.""" + """ + ``addresses`` is not an array. + """ self.assertFilterErrors( { 'addresses': Address(self.trytes1), @@ -138,7 +146,9 @@ def test_fail_addresses_wrong_type(self): ) def test_fail_addresses_empty(self): - """`addresses` is an array, but it's empty.""" + """ + ``addresses`` is an array, but it's empty. + """ self.assertFilterErrors( { 'addresses': [], @@ -150,7 +160,9 @@ def test_fail_addresses_empty(self): ) def test_fail_addresses_contents_invalid(self): - """`addresses` is an array, but it contains invalid values.""" + """ + ``addresses`` is an array, but it contains invalid values. + """ self.assertFilterErrors( { 'addresses': [ @@ -161,7 +173,7 @@ def test_fail_addresses_contents_invalid(self): b'not valid trytes', # This is actually valid; I just added it to make sure the - # filter isn't cheating! + # filter isn't cheating! TryteString(self.trytes2), 2130706433, @@ -181,7 +193,9 @@ def test_fail_addresses_contents_invalid(self): ) def test_fail_threshold_float(self): - """`threshold` is a float.""" + """ + `threshold` is a float. + """ self.assertFilterErrors( { # Even with an empty fpart, floats are not accepted. @@ -196,7 +210,9 @@ def test_fail_threshold_float(self): ) def test_fail_threshold_string(self): - """`threshold` is a string.""" + """ + ``threshold`` is a string. + """ self.assertFilterErrors( { 'threshold': '86', @@ -210,7 +226,9 @@ def test_fail_threshold_string(self): ) def test_fail_threshold_too_small(self): - """`threshold` is less than 0.""" + """ + ``threshold`` is less than 0. + """ self.assertFilterErrors( { 'threshold': -1, @@ -224,7 +242,9 @@ def test_fail_threshold_too_small(self): ) def test_fail_threshold_too_big(self): - """`threshold` is greater than 100.""" + """ + ``threshold`` is greater than 100. + """ self.assertFilterErrors( { 'threshold': 101, diff --git a/test/commands/core/get_inclusion_states_test.py b/test/commands/core/get_inclusion_states_test.py index a09768b..7c3b8dc 100644 --- a/test/commands/core/get_inclusion_states_test.py +++ b/test/commands/core/get_inclusion_states_test.py @@ -7,10 +7,10 @@ import filters as f from filters.test import BaseFilterTestCase from iota import Iota, TransactionHash, TryteString +from iota.adapter import MockAdapter from iota.commands.core.get_inclusion_states import GetInclusionStatesCommand from iota.filters import Trytes from six import binary_type, text_type -from test import MockAdapter class GetInclusionStatesRequestFilterTestCase(BaseFilterTestCase): @@ -32,7 +32,9 @@ def setUp(self): ) def test_pass_happy_path(self): - """Typical `getInclusionStates` request.""" + """ + Typical ``getInclusionStates`` request. + """ request = { 'transactions': [ TransactionHash(self.trytes1), @@ -53,10 +55,35 @@ def test_pass_happy_path(self): self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, request) + def test_pass_optional_parameters_omitted(self): + """ + The request omits optional parameters. + """ + filter_ = self._filter({ + 'transactions': [ + TransactionHash(self.trytes1), + TransactionHash(self.trytes2), + ], + }) + + self.assertFilterPasses(filter_) + self.assertDictEqual( + filter_.cleaned_data, + + { + 'tips': [], + + 'transactions': [ + TransactionHash(self.trytes1), + TransactionHash(self.trytes2), + ], + }, + ) + def test_pass_compatible_types(self): """ The request contains values that can be converted to expected - types. + types. """ filter_ = self._filter({ 'transactions': [ @@ -88,22 +115,24 @@ def test_pass_compatible_types(self): ) def test_fail_empty(self): - """The incoming request is empty.""" + """ + The incoming request is empty. + """ self.assertFilterErrors( {}, { 'transactions': [f.FilterMapper.CODE_MISSING_KEY], - 'tips': [f.FilterMapper.CODE_MISSING_KEY], }, ) def test_fail_unexpected_parameters(self): - """The incoming request contains unexpected parameters.""" + """ + The incoming request contains unexpected parameters. + """ self.assertFilterErrors( { 'transactions': [TransactionHash(self.trytes1)], - 'tips': [TransactionHash(self.trytes2)], # I bring scientists, you bring a rock star. 'foo': 'bar', @@ -115,12 +144,12 @@ def test_fail_unexpected_parameters(self): ) def test_fail_transactions_null(self): - """`transactions` is null.""" + """ + ``transactions`` is null. + """ self.assertFilterErrors( { 'transactions': None, - - 'tips': [TransactionHash(self.trytes2)], }, { @@ -129,14 +158,14 @@ def test_fail_transactions_null(self): ) def test_fail_transactions_wrong_type(self): - """`transactions` is not an array.""" + """ + ``transactions`` is not an array. + """ self.assertFilterErrors( { # Has to be an array, even if we're only querying for one # transaction. 'transactions': TransactionHash(self.trytes1), - - 'tips': [TransactionHash(self.trytes2)], }, { @@ -145,12 +174,12 @@ def test_fail_transactions_wrong_type(self): ) def test_fail_transactions_empty(self): - """`transactions` is an array, but it is empty.""" + """ + ``transactions`` is an array, but it is empty. + """ self.assertFilterErrors( { 'transactions': [], - - 'tips': [TransactionHash(self.trytes2)], }, { @@ -159,7 +188,10 @@ def test_fail_transactions_empty(self): ) def test_fail_transactions_contents_invalid(self): - """`transactions` is an array, but it contains invalid values.""" + """ + ``transactions`` is a non-empty array, but it contains invalid + values. + """ self.assertFilterErrors( { 'transactions': [ @@ -170,14 +202,12 @@ def test_fail_transactions_contents_invalid(self): b'not valid trytes', # This is actually valid; I just added it to make sure the - # filter isn't cheating! + # filter isn't cheating! TryteString(self.trytes1), 2130706433, b'9' * 82, ], - - 'tips': [TransactionHash(self.trytes2)], }, { @@ -191,22 +221,10 @@ def test_fail_transactions_contents_invalid(self): }, ) - def test_fail_tips_null(self): - """`tips` is null""" - self.assertFilterErrors( - { - 'tips': None, - - 'transactions': [TransactionHash(self.trytes1)], - }, - - { - 'tips': [f.Required.CODE_EMPTY], - }, - ) - def test_fail_tips_wrong_type(self): - """`tips` is not an array.""" + """ + ``tips`` is not an array. + """ self.assertFilterErrors( { 'tips': TransactionHash(self.trytes2), @@ -219,22 +237,10 @@ def test_fail_tips_wrong_type(self): }, ) - def test_fail_tips_empty(self): - """`tips` is an array, but it is empty.""" - self.assertFilterErrors( - { - 'tips': [], - - 'transactions': [TransactionHash(self.trytes1)], - }, - - { - 'tips': [f.Required.CODE_EMPTY], - }, - ) - def test_fail_tips_contents_invalid(self): - """`tips` is an array, but it contains invalid values.""" + """ + ``tips`` contains invalid values. + """ self.assertFilterErrors( { 'tips': [ @@ -245,7 +251,7 @@ def test_fail_tips_contents_invalid(self): b'not valid trytes', # This is actually valid; I just added it to make sure the - # filter isn't cheating! + # filter isn't cheating! TryteString(self.trytes1), 2130706433, diff --git a/test/commands/core/get_neighbors_test.py b/test/commands/core/get_neighbors_test.py index 9a0760a..be7888f 100644 --- a/test/commands/core/get_neighbors_test.py +++ b/test/commands/core/get_neighbors_test.py @@ -7,8 +7,8 @@ import filters as f from filters.test import BaseFilterTestCase from iota import Iota +from iota.adapter import MockAdapter from iota.commands.core.get_neighbors import GetNeighborsCommand -from test import MockAdapter class GetNeighborsRequestFilterTestCase(BaseFilterTestCase): @@ -16,14 +16,18 @@ class GetNeighborsRequestFilterTestCase(BaseFilterTestCase): skip_value_check = True def test_pass_empty(self): - """The request is (correctly) empty.""" + """ + The request is (correctly) empty. + """ filter_ = self._filter({}) self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, {}) def test_fail_unexpected_parameters(self): - """The request contains unexpected parameters.""" + """ + The request contains unexpected parameters. + """ self.assertFilterErrors( { # Fool of a Took! diff --git a/test/commands/core/get_node_info_test.py b/test/commands/core/get_node_info_test.py index f09eb79..3c98afa 100644 --- a/test/commands/core/get_node_info_test.py +++ b/test/commands/core/get_node_info_test.py @@ -7,8 +7,8 @@ import filters as f from filters.test import BaseFilterTestCase from iota import Iota, TransactionHash +from iota.adapter import MockAdapter from iota.commands.core.get_node_info import GetNodeInfoCommand -from test import MockAdapter class GetNodeInfoRequestFilterTestCase(BaseFilterTestCase): @@ -16,7 +16,9 @@ class GetNodeInfoRequestFilterTestCase(BaseFilterTestCase): skip_value_check = True def test_pass_empty(self): - """The incoming response is (correctly) empty.""" + """ + The incoming response is (correctly) empty. + """ request = {} filter_ = self._filter(request) @@ -25,7 +27,9 @@ def test_pass_empty(self): self.assertDictEqual(filter_.cleaned_data, request) def test_fail_unexpected_parameters(self): - """The incoming response contains unexpected parameters.""" + """ + The incoming response contains unexpected parameters. + """ self.assertFilterErrors( { # All you had to do was nothing! How did you screw that up?! @@ -44,7 +48,9 @@ class GetNodeInfoResponseFilterTestCase(BaseFilterTestCase): # noinspection SpellCheckingInspection def test_pass_happy_path(self): - """The incoming response contains valid values.""" + """ + The incoming response contains valid values. + """ response = { 'appName': 'IRI', 'appVersion': '1.0.8.nu', diff --git a/test/commands/core/get_tips_test.py b/test/commands/core/get_tips_test.py index b78a231..43f3922 100644 --- a/test/commands/core/get_tips_test.py +++ b/test/commands/core/get_tips_test.py @@ -7,8 +7,8 @@ import filters as f from filters.test import BaseFilterTestCase from iota import Address, Iota +from iota.adapter import MockAdapter from iota.commands.core.get_tips import GetTipsCommand -from test import MockAdapter class GetTipsRequestFilterTestCase(BaseFilterTestCase): @@ -16,7 +16,9 @@ class GetTipsRequestFilterTestCase(BaseFilterTestCase): skip_value_check = True def test_pass_empty(self): - """The incoming response is (correctly) empty.""" + """ + The incoming response is (correctly) empty. + """ request = {} filter_ = self._filter(request) @@ -25,7 +27,9 @@ def test_pass_empty(self): self.assertDictEqual(filter_.cleaned_data, request) def test_fail_unexpected_parameters(self): - """The incoming response contains unexpected parameters.""" + """ + The incoming response contains unexpected parameters. + """ self.assertFilterErrors( { # All you had to do was nothing! How did you screw that up?! @@ -44,7 +48,9 @@ class GetTipsResponseFilterTestCase(BaseFilterTestCase): # noinspection SpellCheckingInspection def test_pass_lots_of_hashes(self): - """The response contains lots of hashes.""" + """ + The response contains lots of hashes. + """ response = { 'hashes': [ 'YVXJOEOP9JEPRQUVBPJMB9MGIB9OMTIJJLIUYPM9' @@ -89,7 +95,9 @@ def test_pass_lots_of_hashes(self): ) def test_pass_no_hashes(self): - """The response doesn't contain any hashes.""" + """ + The response doesn't contain any hashes. + """ response = { 'hashes': [], 'duration': 4, diff --git a/test/commands/core/get_transactions_to_approve_test.py b/test/commands/core/get_transactions_to_approve_test.py index 9c0bdd9..e3c0718 100644 --- a/test/commands/core/get_transactions_to_approve_test.py +++ b/test/commands/core/get_transactions_to_approve_test.py @@ -7,9 +7,9 @@ import filters as f from filters.test import BaseFilterTestCase from iota import Iota, TransactionHash +from iota.adapter import MockAdapter from iota.commands.core.get_transactions_to_approve import \ GetTransactionsToApproveCommand -from test import MockAdapter class GetTransactionsToApproveRequestFilterTestCase(BaseFilterTestCase): diff --git a/test/commands/core/get_trytes_test.py b/test/commands/core/get_trytes_test.py index 77e7d53..c2e8659 100644 --- a/test/commands/core/get_trytes_test.py +++ b/test/commands/core/get_trytes_test.py @@ -7,10 +7,10 @@ import filters as f from filters.test import BaseFilterTestCase from iota import Iota, TransactionHash, TryteString +from iota.adapter import MockAdapter from iota.commands.core.get_trytes import GetTrytesCommand from iota.filters import Trytes from six import binary_type, text_type -from test import MockAdapter class GetTrytesRequestFilterTestCase(BaseFilterTestCase): @@ -34,7 +34,9 @@ def setUp(self): ) def test_pass_happy_path(self): - """The request is valid.""" + """ + The request is valid. + """ request = { 'hashes': [ TransactionHash(self.trytes1), @@ -74,7 +76,9 @@ def test_pass_compatible_types(self): ) def test_fail_empty(self): - """The request is empty.""" + """ + The request is empty. + """ self.assertFilterErrors( {}, @@ -84,7 +88,9 @@ def test_fail_empty(self): ) def test_fail_unexpected_parameters(self): - """The request contains unexpected parameters.""" + """ + The request contains unexpected parameters. + """ self.assertFilterErrors( { 'hashes': [TransactionHash(self.trytes1)], @@ -99,7 +105,9 @@ def test_fail_unexpected_parameters(self): ) def test_fail_hashes_null(self): - """`hashes` is null.""" + """ + ``hashes`` is null. + """ self.assertFilterErrors( { 'hashes': None, @@ -111,10 +119,12 @@ def test_fail_hashes_null(self): ) def test_fail_hashes_wrong_type(self): - """`hashes` is not an array.""" + """ + ``hashes`` is not an array. + """ self.assertFilterErrors( { - # `hashes` must be an array, even if we're only querying + # ``hashes`` must be an array, even if we're only querying # against a single transaction. 'hashes': TransactionHash(self.trytes1), }, @@ -125,7 +135,9 @@ def test_fail_hashes_wrong_type(self): ) def test_fail_hashes_empty(self): - """`hashes` is an array, but it is empty.""" + """ + ``hashes`` is an array, but it is empty. + """ self.assertFilterErrors( { 'hashes': [], @@ -137,7 +149,9 @@ def test_fail_hashes_empty(self): ) def test_fail_hashes_contents_invalid(self): - """`hashes` is an array, but it contains invalid values.""" + """ + ``hashes`` is an array, but it contains invalid values. + """ self.assertFilterErrors( { 'hashes': [ @@ -148,7 +162,7 @@ def test_fail_hashes_contents_invalid(self): b'not valid trytes', # This is actually valid; I just added it to make sure the - # filter isn't cheating! + # filter isn't cheating! TryteString(self.trytes1), 2130706433, @@ -183,7 +197,9 @@ def setUp(self): b'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHDWCTCEAKDCDFD9DSCSA' def test_pass_transactions(self): - """The response contains data for multiple transactions.""" + """ + The response contains data for multiple transactions. + """ filter_ = self._filter({ 'trytes': [ # In real life, these values would be a lot longer, but for the @@ -210,7 +226,9 @@ def test_pass_transactions(self): ) def test_pass_no_transactions(self): - """The response does not contain any transactions.""" + """ + The response does not contain any transactions. + """ response = { 'trytes': [], 'duration': 42, diff --git a/test/commands/core/interrupt_attaching_to_tangle_test.py b/test/commands/core/interrupt_attaching_to_tangle_test.py index 4b1e6e3..4580b86 100644 --- a/test/commands/core/interrupt_attaching_to_tangle_test.py +++ b/test/commands/core/interrupt_attaching_to_tangle_test.py @@ -7,9 +7,9 @@ import filters as f from filters.test import BaseFilterTestCase from iota import Iota +from iota.adapter import MockAdapter from iota.commands.core.interrupt_attaching_to_tangle import \ InterruptAttachingToTangleCommand -from test import MockAdapter class InterruptAttachingToTangleRequestFilterTestCase(BaseFilterTestCase): @@ -24,7 +24,9 @@ def test_pass_empty(self): self.assertDictEqual(filter_.cleaned_data, {}) def test_fail_unexpected_parameters(self): - """The request contains unexpected parameters.""" + """ + The request contains unexpected parameters. + """ self.assertFilterErrors( { # You're tearing me apart Lisa! diff --git a/test/commands/core/remove_neighbors_test.py b/test/commands/core/remove_neighbors_test.py index 3740e04..90a7134 100644 --- a/test/commands/core/remove_neighbors_test.py +++ b/test/commands/core/remove_neighbors_test.py @@ -7,9 +7,9 @@ import filters as f from filters.test import BaseFilterTestCase from iota import Iota +from iota.adapter import MockAdapter from iota.commands.core.remove_neighbors import RemoveNeighborsCommand from iota.filters import NodeUri -from test import MockAdapter class RemoveNeighborsRequestFilterTestCase(BaseFilterTestCase): @@ -17,7 +17,9 @@ class RemoveNeighborsRequestFilterTestCase(BaseFilterTestCase): skip_value_check = True def test_pass_valid_request(self): - """The incoming request is valid.""" + """ + The incoming request is valid. + """ request = { 'uris': [ 'udp://node1.iotatoken.com', @@ -31,7 +33,9 @@ def test_pass_valid_request(self): self.assertDictEqual(filter_.cleaned_data, request) def test_fail_empty(self): - """The incoming request is empty.""" + """ + The incoming request is empty. + """ self.assertFilterErrors( {}, @@ -41,7 +45,9 @@ def test_fail_empty(self): ) def test_fail_unexpected_parameters(self): - """The incoming request contains unexpected parameters.""" + """ + The incoming request contains unexpected parameters. + """ self.assertFilterErrors( { 'uris': ['udp://localhost'], @@ -56,7 +62,9 @@ def test_fail_unexpected_parameters(self): ) def test_fail_neighbors_null(self): - """`uris` is null.""" + """ + ``uris`` is null. + """ self.assertFilterErrors( { 'uris': None, @@ -68,11 +76,13 @@ def test_fail_neighbors_null(self): ) def test_fail_uris_wrong_type(self): - """`uris` is not an array.""" + """ + ``uris`` is not an array. + """ self.assertFilterErrors( { # Nope; it's gotta be an array, even if you only want to add - # a single neighbor. + # a single neighbor. 'uris': 'http://localhost:8080/' }, @@ -82,7 +92,9 @@ def test_fail_uris_wrong_type(self): ) def test_fail_uris_empty(self): - """`uris` is an array, but it's empty.""" + """ + ``uris`` is an array, but it's empty. + """ self.assertFilterErrors( { # Insert "Forever Alone" meme here. @@ -96,12 +108,12 @@ def test_fail_uris_empty(self): def test_fail_uris_contents_invalid(self): """ - `uris` is an array, but it contains invalid values. + ``uris`` is an array, but it contains invalid values. """ self.assertFilterErrors( { # When I said it has to be an array before, I meant an array of - # strings! + # strings! 'uris': [ '', False, @@ -110,7 +122,7 @@ def test_fail_uris_contents_invalid(self): 'not a valid uri', # This is actually valid; I just added it to make sure the - # filter isn't cheating! + # filter isn't cheating! 'udp://localhost', 2130706433, diff --git a/test/commands/core/store_transactions_test.py b/test/commands/core/store_transactions_test.py index 1e3d169..edd15f8 100644 --- a/test/commands/core/store_transactions_test.py +++ b/test/commands/core/store_transactions_test.py @@ -6,11 +6,11 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Iota, TryteString +from iota import Iota, TransactionTrytes, TryteString +from iota.adapter import MockAdapter from iota.commands.core.store_transactions import StoreTransactionsCommand from iota.filters import Trytes from six import binary_type, text_type -from test import MockAdapter class StoreTransactionsRequestFilterTestCase(BaseFilterTestCase): @@ -22,17 +22,19 @@ def setUp(self): super(StoreTransactionsRequestFilterTestCase, self).setUp() # Define a few valid values here that we can reuse across multiple - # tests. + # tests. self.trytes1 = b'RBTC9D9DCDQAEASBYBCCKBFA' self.trytes2 =\ b'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHDWCTCEAKDCDFD9DSCSA' def test_pass_happy_path(self): - """The incoming request is valid.""" + """ + The incoming request is valid. + """ request = { 'trytes': [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ], } @@ -44,7 +46,7 @@ def test_pass_happy_path(self): def test_pass_compatible_types(self): """ The incoming request contains values that can be converted into the - expected types. + expected types. """ # Any values that can be converted into TryteStrings are accepted. filter_ = self._filter({ @@ -59,17 +61,19 @@ def test_pass_compatible_types(self): filter_.cleaned_data, # The values are converted into TryteStrings so that they can be - # sent to the node. + # sent to the node. { 'trytes': [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ], }, ) def test_fail_empty(self): - """The incoming request is empty.""" + """ + The incoming request is empty. + """ self.assertFilterErrors( {}, @@ -79,7 +83,9 @@ def test_fail_empty(self): ) def test_fail_unexpected_parameters(self): - """The incoming value contains unexpected parameters.""" + """ + The incoming value contains unexpected parameters. + """ self.assertFilterErrors( { 'trytes': [TryteString(self.trytes1)], @@ -94,7 +100,9 @@ def test_fail_unexpected_parameters(self): ) def test_fail_trytes_null(self): - """`trytes` is null.""" + """ + ``trytes`` is null. + """ self.assertFilterErrors( { 'trytes': None, @@ -106,11 +114,13 @@ def test_fail_trytes_null(self): ) def test_fail_trytes_wrong_type(self): - """`trytes` is not an array.""" + """ + ``trytes`` is not an array. + """ self.assertFilterErrors( { - # `trytes` has to be an array, even if there's only one - # TryteString. + # ``trytes`` has to be an array, even if there's only one + # TryteString. 'trytes': TryteString(self.trytes1), }, @@ -120,7 +130,9 @@ def test_fail_trytes_wrong_type(self): ) def test_fail_trytes_empty(self): - """`trytes` is an array, but it's empty.""" + """ + ``trytes`` is an array, but it's empty. + """ self.assertFilterErrors( { 'trytes': [], @@ -132,7 +144,9 @@ def test_fail_trytes_empty(self): ) def test_trytes_contents_invalid(self): - """`trytes` is an array, but it contains invalid values.""" + """ + ``trytes`` is an array, but it contains invalid values. + """ self.assertFilterErrors( { 'trytes': [ @@ -143,10 +157,12 @@ def test_trytes_contents_invalid(self): b'not valid trytes', # This is actually valid; I just added it to make sure the - # filter isn't cheating! + # filter isn't cheating! TryteString(self.trytes2), 2130706433, + + b'9' * (TransactionTrytes.LEN + 1), ], }, @@ -157,6 +173,7 @@ def test_trytes_contents_invalid(self): 'trytes.3': [f.Required.CODE_EMPTY], 'trytes.4': [Trytes.CODE_NOT_TRYTES], 'trytes.6': [f.Type.CODE_WRONG_TYPE], + 'trytes.7': [Trytes.CODE_WRONG_FORMAT], }, ) diff --git a/test/commands/extended/broadcast_and_store_test.py b/test/commands/extended/broadcast_and_store_test.py index 83c1a58..0bbc570 100644 --- a/test/commands/extended/broadcast_and_store_test.py +++ b/test/commands/extended/broadcast_and_store_test.py @@ -4,10 +4,10 @@ from unittest import TestCase -from iota import BadApiResponse, Iota, TryteString +from iota import BadApiResponse, Iota, TransactionTrytes, TryteString +from iota.adapter import MockAdapter from iota.commands.extended.broadcast_and_store import BroadcastAndStoreCommand from six import text_type -from test import MockAdapter class BroadcastAndStoreCommandTestCase(TestCase): @@ -32,7 +32,7 @@ def test_wireup(self): def test_happy_path(self): """ - Successful invocation of `broadcastAndStore`. + Successful invocation of ``broadcastAndStore``. """ self.adapter.seed_response('broadcastTransactions', { 'trytes': [ @@ -44,8 +44,8 @@ def test_happy_path(self): self.adapter.seed_response('storeTransactions', {}) trytes = [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ] response = self.command(trytes=trytes) @@ -70,14 +70,14 @@ def test_happy_path(self): def test_broadcast_transactions_fails(self): """ - The `broadcastTransactions` command fails. + The `broadcastTransactions`` command fails. """ self.adapter.seed_response('broadcastTransactions', { 'error': "I'm a teapot.", }) with self.assertRaises(BadApiResponse): - self.command(trytes=[TryteString(self.trytes1)]) + self.command(trytes=[TransactionTrytes(self.trytes1)]) # The command stopped after the first request failed. self.assertListEqual( @@ -85,13 +85,13 @@ def test_broadcast_transactions_fails(self): [{ 'command': 'broadcastTransactions', - 'trytes': [TryteString(self.trytes1)], + 'trytes': [TransactionTrytes(self.trytes1)], }], ) def test_store_transactions_fails(self): """ - The `storeTransactions` command fails. + The ``storeTransactions`` command fails. """ self.adapter.seed_response('broadcastTransactions', { 'trytes': [ @@ -105,7 +105,7 @@ def test_store_transactions_fails(self): }) with self.assertRaises(BadApiResponse): - self.command(trytes=[TryteString(self.trytes1)]) + self.command(trytes=[TransactionTrytes(self.trytes1)]) # The `broadcastTransactions` command was still executed; there is # no way to execute these commands atomically. @@ -115,12 +115,12 @@ def test_store_transactions_fails(self): [ { 'command': 'broadcastTransactions', - 'trytes': [TryteString(self.trytes1)], + 'trytes': [TransactionTrytes(self.trytes1)], }, { 'command': 'storeTransactions', - 'trytes': [TryteString(self.trytes1)], + 'trytes': [TransactionTrytes(self.trytes1)], }, ], ) diff --git a/test/commands/extended/get_bundles_test.py b/test/commands/extended/get_bundles_test.py index 45ec6cc..3d06fa4 100644 --- a/test/commands/extended/get_bundles_test.py +++ b/test/commands/extended/get_bundles_test.py @@ -7,10 +7,10 @@ import filters as f from filters.test import BaseFilterTestCase from iota import Iota, TransactionHash +from iota.adapter import MockAdapter from iota.commands.extended.get_bundles import GetBundlesCommand from iota.filters import Trytes from six import binary_type, text_type -from test import MockAdapter class GetBundlesRequestFilterTestCase(BaseFilterTestCase): diff --git a/test/commands/extended/get_inputs_test.py b/test/commands/extended/get_inputs_test.py index e4b3ee1..41a1761 100644 --- a/test/commands/extended/get_inputs_test.py +++ b/test/commands/extended/get_inputs_test.py @@ -7,12 +7,12 @@ import filters as f from filters.test import BaseFilterTestCase from iota import Iota +from iota.adapter import MockAdapter from iota.commands.extended.get_inputs import GetInputsCommand, \ GetInputsRequestFilter from iota.crypto.types import Seed from iota.filters import Trytes from six import binary_type, text_type -from test import MockAdapter class GetInputsRequestFilterTestCase(BaseFilterTestCase): diff --git a/test/commands/extended/get_latest_inclusion_test.py b/test/commands/extended/get_latest_inclusion_test.py index 3b550c0..1351d41 100644 --- a/test/commands/extended/get_latest_inclusion_test.py +++ b/test/commands/extended/get_latest_inclusion_test.py @@ -6,12 +6,13 @@ import filters as f from filters.test import BaseFilterTestCase +from six import binary_type, text_type + from iota import Iota, TransactionHash, TryteString +from iota.adapter import MockAdapter from iota.commands.extended.get_latest_inclusion import \ GetLatestInclusionCommand from iota.filters import Trytes -from six import binary_type, text_type -from test import MockAdapter class GetLatestInclusionRequestFilterTestCase(BaseFilterTestCase): @@ -178,12 +179,32 @@ def test_fail_hashes_contents_invalid(self): class GetLatestInclusionCommandTestCase(TestCase): + # noinspection SpellCheckingInspection def setUp(self): super(GetLatestInclusionCommandTestCase, self).setUp() self.adapter = MockAdapter() self.command = GetLatestInclusionCommand(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. @@ -197,5 +218,26 @@ def test_happy_path(self): """ Successfully requesting latest inclusion state. """ - # :todo: Implement test. - self.skipTest('Not implemented yet.') + self.adapter.seed_response('getNodeInfo', { + # ``getNodeInfo`` returns lots of info, but the only value that + # matters for this test is ``latestSolidSubtangleMilestone``. + 'latestSolidSubtangleMilestone': self.milestone, + }, + ) + + self.adapter.seed_response('getInclusionStates', { + 'states': [True, False], + }) + + response = self.command(hashes=[self.hash1, self.hash2]) + + self.assertDictEqual( + response, + + { + 'states': { + self.hash1: True, + self.hash2: False, + }, + } + ) diff --git a/test/commands/extended/get_new_addresses_test.py b/test/commands/extended/get_new_addresses_test.py index 78b7920..b7d30ae 100644 --- a/test/commands/extended/get_new_addresses_test.py +++ b/test/commands/extended/get_new_addresses_test.py @@ -7,12 +7,12 @@ import filters as f from filters.test import BaseFilterTestCase from iota import Address, Iota +from iota.adapter import MockAdapter from iota.commands.extended.get_new_addresses import GetNewAddressesCommand from iota.crypto.types import Seed from iota.filters import Trytes from mock import patch from six import binary_type, text_type -from test import MockAdapter class GetNewAddressesRequestFilterTestCase(BaseFilterTestCase): diff --git a/test/commands/extended/get_transfers_test.py b/test/commands/extended/get_transfers_test.py index bb8c02e..cf8b50c 100644 --- a/test/commands/extended/get_transfers_test.py +++ b/test/commands/extended/get_transfers_test.py @@ -6,15 +6,14 @@ import filters as f from filters.test import BaseFilterTestCase -from mock import Mock, patch -from six import binary_type, text_type - -from iota import Address, Iota, Bundle, Tag, Transaction +from iota import Address, Iota, Bundle, Tag, Transaction, TryteString +from iota.adapter import MockAdapter from iota.commands.extended.get_transfers import GetTransfersCommand, \ GetTransfersRequestFilter from iota.crypto.types import Seed from iota.filters import Trytes -from test import MockAdapter +from mock import Mock, patch +from six import binary_type, text_type class GetTransfersRequestFilterTestCase(BaseFilterTestCase): @@ -35,7 +34,7 @@ def test_pass_happy_path(self): request = { 'seed': Seed(self.seed), 'start': 0, - 'end': 10, + 'stop': 10, 'inclusion_states': True, } @@ -56,7 +55,7 @@ def test_pass_compatible_types(self): # These values must still be integers/bools, however. 'start': 42, - 'end': 86, + 'stop': 86, 'inclusion_states': True, }) @@ -67,7 +66,7 @@ def test_pass_compatible_types(self): { 'seed': Seed(self.seed), 'start': 42, - 'end': 86, + 'stop': 86, 'inclusion_states': True, }, ) @@ -87,7 +86,7 @@ def test_pass_optional_parameters_excluded(self): { 'seed': Seed(self.seed), 'start': 0, - 'end': None, + 'stop': None, 'inclusion_states': False, } ) @@ -214,65 +213,65 @@ def test_fail_start_too_small(self): }, ) - def test_fail_end_string(self): + def test_fail_stop_string(self): """ - ``end`` is a string. + ``stop`` is a string. """ self.assertFilterErrors( { # Not valid; it must be an int. - 'end': '0', + 'stop': '0', 'seed': Seed(self.seed), }, { - 'end': [f.Type.CODE_WRONG_TYPE], + 'stop': [f.Type.CODE_WRONG_TYPE], }, ) - def test_fail_end_float(self): + def test_fail_stop_float(self): """ - ``end`` is a float. + ``stop`` is a float. """ self.assertFilterErrors( { # Even with an empty fpart, floats are not valid. # It's gotta be an int. - 'end': 8.0, + 'stop': 8.0, 'seed': Seed(self.seed), }, { - 'end': [f.Type.CODE_WRONG_TYPE], + 'stop': [f.Type.CODE_WRONG_TYPE], }, ) - def test_fail_end_too_small(self): + def test_fail_stop_too_small(self): """ - ``end`` is less than 0. + ``stop`` is less than 0. """ self.assertFilterErrors( { - 'end': -1, + 'stop': -1, 'seed': Seed(self.seed), }, { - 'end': [f.Min.CODE_TOO_SMALL], + 'stop': [f.Min.CODE_TOO_SMALL], }, ) - def test_fail_end_occurs_before_start(self): + def test_fail_stop_occurs_before_start(self): """ - ``end`` is less than ``start``. + ``stop`` is less than ``start``. """ self.assertFilterErrors( { 'start': 1, - 'end': 0, + 'stop': 0, 'seed': Seed(self.seed), }, @@ -284,18 +283,18 @@ def test_fail_end_occurs_before_start(self): def test_fail_interval_too_large(self): """ - ``end`` is way more than ``start``. + ``stop`` is way more than ``start``. """ self.assertFilterErrors( { 'start': 0, - 'end': GetTransfersRequestFilter.MAX_INTERVAL + 1, + 'stop': GetTransfersRequestFilter.MAX_INTERVAL + 1, 'seed': Seed(self.seed), }, { - 'end': [GetTransfersRequestFilter.CODE_INTERVAL_TOO_BIG], + 'stop': [GetTransfersRequestFilter.CODE_INTERVAL_TOO_BIG], }, ) @@ -324,6 +323,19 @@ def setUp(self): self.adapter = MockAdapter() self.command = GetTransfersCommand(self.adapter) + # Define some tryte sequences we can re-use between tests. + self.addy1 =\ + Address( + b'TESTVALUEONE9DONTUSEINPRODUCTION99999YDZ' + b'E9TAFAJGJA9CECKDAEPHBICDR9LHFCOFRBQDHC9IG' + ) + + self.addy2 =\ + Address( + b'TESTVALUETWO9DONTUSEINPRODUCTION99999TES' + b'GINEIDLEEHRAOGEBMDLENFDAFCHEIHZ9EBZDD9YHL' + ) + def test_wireup(self): """ Verify that the command is wired up correctly. @@ -337,25 +349,13 @@ def test_full_scan(self): """ Scanning the Tangle for all transfers. """ - addy1 =\ - Address( - b'TESTVALUEONE9DONTUSEINPRODUCTION99999YDZ' - b'E9TAFAJGJA9CECKDAEPHBICDR9LHFCOFRBQDHC9IG' - ) - - addy2 =\ - Address( - b'TESTVALUETWO9DONTUSEINPRODUCTION99999TES' - b'GINEIDLEEHRAOGEBMDLENFDAFCHEIHZ9EBZDD9YHL' - ) - # To speed up the test, we will mock the address generator. # :py:class:`iota.crypto.addresses.AddressGenerator` already has # its own test case, so this does not impact the stability of the # codebase. # noinspection PyUnusedLocal def create_generator(ag, start, step=1): - for addy in [addy1, addy2][start::step]: + for addy in [self.addy1, self.addy2][start::step]: yield addy # The first address received IOTA. @@ -397,7 +397,7 @@ def create_generator(ag, start, step=1): bundle = Bundle([ Transaction( - address = addy1, + address = self.addy1, timestamp = 1483033814, # These values are not relevant to the test. @@ -436,30 +436,308 @@ def create_generator(ag, start, step=1): }, ) - def test_start(self): + def test_no_transactions(self): """ - Scanning the Tangle for all transfers, with start index. + There are no transactions for the specified seed. """ - # :todo: Implement test. - self.skipTest('Not implemented yet.') + # To speed up the test, we will mock the address generator. + # :py:class:`iota.crypto.addresses.AddressGenerator` already has + # its own test case, so this does not impact the stability of the + # codebase. + # noinspection PyUnusedLocal + def create_generator(ag, start, step=1): + for addy in [self.addy1][start::step]: + yield addy + + self.adapter.seed_response( + 'findTransactions', - def test_end(self): + { + 'duration': 1, + 'hashes': [], + }, + ) + + with patch( + 'iota.crypto.addresses.AddressGenerator.create_generator', + create_generator, + ): + response = self.command(seed=Seed.random()) + + self.assertDictEqual(response, {'bundles': []}) + + def test_start(self): """ - Scanning the Tangle for all transfers, with end index. + Scanning the Tangle for all transfers, with start index. """ - # :todo: Implement test. - self.skipTest('Not implemented yet.') + # noinspection PyUnusedLocal + def create_generator(ag, start, step=1): + # Inject an invalid value into the generator, to ensure it is + # skipped. + for addy in [None, self.addy1, self.addy2][start::step]: + yield addy + + # The first address received IOTA. + self.adapter.seed_response( + 'findTransactions', + + { + 'duration': 42, + + 'hashes': [ + 'TESTVALUEFIVE9DONTUSEINPRODUCTION99999VH' + 'YHRHJETGYCAFZGABTEUBWCWAS9WF99UHBHRHLIOFJ', + ], + }, + ) + + # The second address is unused. + self.adapter.seed_response( + 'findTransactions', + + { + 'duration': 1, + 'hashes': [], + }, + ) + + self.adapter.seed_response( + 'getTrytes', + + { + 'duration': 99, + 'trytes': [''], + }, + ) + + bundle = Bundle([ + Transaction( + address = self.addy1, + timestamp = 1483033814, + + # These values are not relevant to the test. + hash_ = None, + signature_message_fragment = None, + value = 42, + tag = Tag(b''), + current_index = 0, + last_index = 0, + bundle_hash = None, + trunk_transaction_hash = None, + branch_transaction_hash = None, + nonce = None, + ) + ]) + + mock_get_bundles = Mock(return_value={ + 'bundles': [bundle], + }) + + with patch( + 'iota.crypto.addresses.AddressGenerator.create_generator', + create_generator, + ): + with patch( + 'iota.commands.extended.get_bundles.GetBundlesCommand._execute', + mock_get_bundles, + ): + response = self.command(seed=Seed.random(), start=1) + + self.assertDictEqual( + response, - def test_start_and_end(self): + { + 'bundles': [bundle], + }, + ) + + def test_stop(self): """ - Scanning the Tangle for all transfers, with start and end indices. + Scanning the Tangle for all transfers, with stop index. """ - # :todo: Implement test. - self.skipTest('Not implemented yet.') + # noinspection PyUnusedLocal + def create_generator(ag, start, step=1): + # Inject an invalid value into the generator, to ensure it is + # skipped. + for addy in [self.addy1, None][start::step]: + yield addy + + # The first address received IOTA. + self.adapter.seed_response( + 'findTransactions', + + { + 'duration': 42, + + 'hashes': [ + 'TESTVALUEFIVE9DONTUSEINPRODUCTION99999VH' + 'YHRHJETGYCAFZGABTEUBWCWAS9WF99UHBHRHLIOFJ', + ], + }, + ) + + self.adapter.seed_response( + 'getTrytes', + + { + 'duration': 99, + 'trytes': [''], + }, + ) + + bundle = Bundle([ + Transaction( + address = self.addy1, + timestamp = 1483033814, + + # These values are not relevant to the test. + hash_ = None, + signature_message_fragment = None, + value = 42, + tag = Tag(b''), + current_index = 0, + last_index = 0, + bundle_hash = None, + trunk_transaction_hash = None, + branch_transaction_hash = None, + nonce = None, + ) + ]) + + mock_get_bundles = Mock(return_value={ + 'bundles': [bundle], + }) + + with patch( + 'iota.crypto.addresses.AddressGenerator.create_generator', + create_generator, + ): + with patch( + 'iota.commands.extended.get_bundles.GetBundlesCommand._execute', + mock_get_bundles, + ): + response = self.command(seed=Seed.random(), stop=1) + + self.assertDictEqual( + response, + + { + 'bundles': [bundle], + }, + ) def test_get_inclusion_states(self): """ Fetching inclusion states with transactions. """ - # :todo: Implement test. - self.skipTest('Not implemented yet.') + # noinspection PyUnusedLocal + def create_generator(ag, start, step=1): + for addy in [self.addy1][start::step]: + yield addy + + # The first address received IOTA. + self.adapter.seed_response( + 'findTransactions', + + { + 'duration': 42, + + 'hashes': [ + 'TESTVALUEFIVE9DONTUSEINPRODUCTION99999VH' + 'YHRHJETGYCAFZGABTEUBWCWAS9WF99UHBHRHLIOFJ', + ], + }, + ) + + # For this test, we have to generate a real TryteString. + transaction_trytes =\ + TryteString( + b'KMYUMNEUAYODAQSNGWTAERRRHNZBZCOLMVVOBTVWLOFYCJKYMGRAMH9RQ9MTZOSZMH' + b'QNZFHFEJEDFQ99HSUNVOTULDJGXEDULS9ZHABVDZODJUMCNWVCPNSCUVKVYWCEXBHW' + b'RBZBSWFPQLWZWMUPGQIGAEGOVE9DDXBVCIPKQYCFZFBELTSMVFSIXLPTACTKAFMCTK' + b'CPYD9BWDJMLKWAOBDSJNQYAHS9GFIQKZCROLFZJVUEIVXVNBRRLEIWTYVHURUXHSCG' + b'DKEIEGPOCXKCYWIBUG9ABYCALYJVFLBNGMS9ARHGTQXBZFLENXCJVKHPVKD9KSAEOL' + b'FFVAJCNKLDVHOCDARWUNKARDYMVKFKRSMUTYOUXSBFFYTKRREBDJZTLVUROQFCBXQN' + b'SXDDYTZTEBRSXOBMLXHJKSJAVOOVCXATOWNQDWHT9CCUAAJUJKDOQLMAEZACSNFKXZ' + b'IGWDQEUEFRZYAOSDNVMSXWYLVDAUXZSHNHAIBEMNPFUGORYUETNJK9UCEMSUJYBBDK' + b'BHIPKEINQCGOVYCPKUPJMUCUVZOJSIWYRFMFXYUVSMOUALAQBWIMXBUBXSAETGKJRP' + b'AHVAXHQJDMEVSRFYEXUSIEBKMGYCUKFD9JPGUV9AIYUVCRUURKMYUHMVE9OJCYYWTQ' + b'WUWFMTBZYFXASHHVCMSWXKBRQFHHQVEQMEULJRWZKLWFFSGGKEHUZZFNDNITSRAUH9' + b'PQK9OGLYMVBSHXQLLZHOBBIM9KVUWDLHZRDKQQVLQXGWYXEEVQPDZUO9PVXMALOMRQ' + b'VCTHGIZLILSCFKTBRESYZGBZKHXEODNDJZ9GK9ROWYXNGFHZCCBHHZEYEOGWXRGSUD' + b'SUZFUAUBXVXZHCUVJSYBWTCYCEDYKZNGWFZYKSQLW9FUYMWDVXKZEWT9SCVMQCODZK' + b'DRNKTINTPNOJOLGQJDAJMFWRFSWZJLYZGSTSIDSXLUJBZRZNLEDNBKAUNGTCYUPDRW' + b'JOCEBQ9YG9IZLLRMJITISJOTLQMOGXVQIZXHMTJVMMWM9FOIOT9KFZMANEPOEOV9HX' + b'JNEGURUKRWDGYNPVGAWMWQVABIJNL9MDXKONEPMYACOZ9BE9UZMAFTKYWPFWIQWAPK' + b'GUXQTOQVWYYVZYGQDLBIQDVOZIWGOMGOBAUARICQZVNXD9UVEFBBAJKQBHRHXTBUOW' + b'VBFKYQWZWTMMXVKZRIZUBVPQ9XHLJHFHWFZUIZVSNAKBDHDFGJCYQETOMEDTOXIUT9' + b'OAJVIHWAGTCNPEZTERMMN9EZEWSJHKQAUMXPBZTNQOEQCVXIMAAYO9NIUFLTCFIMK9' + b'9AFAGWJFA9VOFPUDJLRAMORGSUDBLWWKXEDZ9XPQUZSGANGESHKKGGQSGSYDCRLHZD' + b'PKA9HKYBKLKKCXYRQQIPXCFETJJDZYPCLUNHGBKEJDRCIHEXKCQQNOV9QFHLGFXOCR' + b'HPAFCUTPMY9NOZVQHROYJSCMGRSVMOBWADAZNFIAHWGIQUUZBOVODSFAUNRTXSDU9W' + b'EIRBXQNRSJXFRAQGHA9DYOQJGLVZUJKAQ9CTUOTT9ZKQOQNNLJDUPDXZJYPRCVLRZT' + b'UCZPNBREYCCKHK9FUWGITAJATFPUOFLZDHPNJYUTXFGNYJOBRD9BVHKZENFXIUYDTL' + b'CE9JYIIYMXMCXMWTHOLTQFKFHDLVPGMQNITEUXSYLAQULCZOJVBIPYP9M9X9QCNKBX' + b'W9DVJEQFFY9KQVMKNVTAHQVRXUKEM9FZOJLHAGEECZBUHOQFZOSPRXKZOCCKAOHMSV' + b'QCFG9CWAHKVWNA9QTLYQI9NKOSHWJCNGPJBLEQPUIWJBIOAWKLBXUCERTSL9FVCLYN' + b'ADPYTPKJOIEMAQGWBVGSRCZINXEJODUDCT9FHOUMQM9ZHRMBJYSOMPNMEAJGEHICJI' + b'PVXRKCYX9RZVT9TDZIMXGZJAIYJRGIVMSOICSUINRBQILMJOUQYXCYNJ9WGGJFHYTU' + b'LWOIPUXXFNTIFNOJRZFSQQNAWBQZOLHHLVGHEPWTKKQEVIPVWZUN9ZBICZ9DZZBVII' + b'BF9EPHARZJUFJGBQXQFQIBUECAWRSEKYJNYKNSVBCOWTFBZ9NAHFSAMRBPEYGPRGKW' + b'WTWACZOAPEOECUO9OTMGABJVAIICIPXGSXACVINSYEQFTRCQPCEJXZCY9XZWVWVJRZ' + b'CYEYNFUUBKPWCHICGJZXKE9GSUDXZYUAPLHAKAHYHDXNPHENTERYMMBQOPSQIDENXK' + b'LKCEYCPVTZQLEEJVYJZV9BWU999999999999999999999999999FFL999999999999' + b'9999999999999RJQGVD99999999999A99999999USGBXHGJUEWAUAKNPPRHJXDDMQV' + b'YDSYZJSDWFYLOQVFGBOSLE9KHFDLDYHUYTXVSFAFCOCLQUHJXTEIQRNBTLHEGJFGVF' + b'DJCE9IKAOCSYHLCLWPVVNWNESKLYAJG9FGGZOFXCEYOTWLVIJUHGY9QCU9FMZJY999' + b'9999HYBUYQKKRNAVDPVGYBTVDZ9SVQBLCCVLJTPEQWWOIG9CQZIFQKCROH9YHUCNJT' + b'SYPBVZVBNESX999999D9TARGPQTNIYRZURQGVHCAWEDRBJIIEJIUZYENVE9LLJQMXH' + b'GSUUYUCPSOWBCXVFDCHHAZUDC9LUODYWO' + ) + + self.adapter.seed_response( + 'getTrytes', + + { + 'duration': 99, + 'trytes': [binary_type(transaction_trytes)], + }, + ) + + transaction = Transaction.from_tryte_string(transaction_trytes) + + mock_get_bundles = Mock(return_value={ + 'bundles': [Bundle([transaction])], + }) + + mock_get_latest_inclusion = Mock(return_value={ + 'states': { + transaction.hash: True, + }, + }) + + with patch( + 'iota.crypto.addresses.AddressGenerator.create_generator', + create_generator, + ): + with patch( + 'iota.commands.extended.get_bundles.GetBundlesCommand._execute', + mock_get_bundles, + ): + with patch( + 'iota.commands.extended.get_latest_inclusion.GetLatestInclusionCommand._execute', + mock_get_latest_inclusion, + ): + response = self.command( + seed = Seed.random(), + + inclusion_states = True, + + # To keep the test focused, only retrieve a single + # transaction. + start = 0, + stop = 1, + ) + + bundle = response['bundles'][0] # type: Bundle + self.assertTrue(bundle[0].is_confirmed) diff --git a/test/commands/extended/prepare_transfer_test.py b/test/commands/extended/prepare_transfer_test.py index 1e418aa..55041dd 100644 --- a/test/commands/extended/prepare_transfer_test.py +++ b/test/commands/extended/prepare_transfer_test.py @@ -11,12 +11,12 @@ from iota import Address, BadApiResponse, Iota, ProposedTransaction, Tag, \ TryteString +from iota.adapter import MockAdapter from iota.commands.extended.prepare_transfer import PrepareTransferCommand from iota.crypto.addresses import AddressGenerator from iota.crypto.types import Seed from iota.filters import GeneratedAddress, Trytes from six import PY2, binary_type, text_type -from test import MockAdapter class PrepareTransferRequestFilterTestCase(BaseFilterTestCase): diff --git a/test/commands/extended/replay_bundle_test.py b/test/commands/extended/replay_bundle_test.py index 1162edd..5ac9c3f 100644 --- a/test/commands/extended/replay_bundle_test.py +++ b/test/commands/extended/replay_bundle_test.py @@ -6,11 +6,14 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Iota, TransactionHash +from mock import Mock, patch +from six import binary_type, text_type + +from iota import Address, Bundle, BundleHash, Fragment, Hash, Iota, Tag, \ + Transaction, TransactionHash +from iota.adapter import MockAdapter from iota.commands.extended.replay_bundle import ReplayBundleCommand from iota.filters import Trytes -from six import binary_type, text_type -from test import MockAdapter class ReplayBundleRequestFilterTestCase(BaseFilterTestCase): @@ -296,6 +299,7 @@ def setUp(self): super(ReplayBundleCommandTestCase, self).setUp() self.adapter = MockAdapter() + self.command = ReplayBundleCommand(self.adapter) def test_wireup(self): """ @@ -306,4 +310,297 @@ def test_wireup(self): ReplayBundleCommand, ) - # :todo: Unit tests. + def test_happy_path(self): + """ + Successfully replaying a bundle. + """ + # noinspection SpellCheckingInspection + bundle = Bundle([ + # "Spend" transaction, Part 1 of 1 + Transaction( + hash_ = + TransactionHash( + b'LUQJUUDAZIHSTPBLCZYXWXYKXTFCOCQJ9EHXKLEB' + b'IJBPSRFSBYRBYODDAZ9NPKPYSMPVNEFXYZQ999999' + ), + + address = + Address( + b'FZXUHBBLASPIMBDIHYTDFCDFIRII9LRJPXFTQTPO' + b'VLEIFE9NWTFPPQZHDCXYUOUCXHHNRPKCIROYYTWSA' + ), + + branch_transaction_hash = + TransactionHash( + b'UKGIAYNLALFGJOVUZYJGNIOZSXBBZDXVQLUMHGQE' + b'PZJWYDMGTPJIQXS9GOKXR9WIGWFRWRSKGCJ999999' + ), + + bundle_hash = + BundleHash( + b'ZSATLX9HDENCIARERVLWYHXPQETFL9QKTNC9LUOL' + b'CDXKKW9MYTLZJDXBNOHURUXSYWMGGD9UDGLHCSZO9' + ), + + nonce = + Hash( + b'LIJVXBVTYMEEPCKJRIQTGAKWJRAMYNPJEGHEWAUL' + b'XPBBUQPCJTJPRZTISQPJRJGMSBGQLER9OXYQXFGQO' + ), + + trunk_transaction_hash = + TransactionHash( + b'KFCQSGDYENCECCPNNZDVDTBINCBRBERPTQIHFH9G' + b'YLTCSGUFMVWWSAHVZFXDVEZO9UHAUIU9LNX999999' + ), + + signature_message_fragment = Fragment(b''), + + current_index = 0, + last_index = 3, + tag = Tag(b''), + timestamp = 1483033814, + value = 1, + ), + + # Input #1, Part 1 of 2 + Transaction( + hash_ = + TransactionHash( + b'KFCQSGDYENCECCPNNZDVDTBINCBRBERPTQIHFH9G' + b'YLTCSGUFMVWWSAHVZFXDVEZO9UHAUIU9LNX999999' + ), + + address = + Address( + b'GMHOSRRGXJLDPVWRWVSRWI9BCIVLUXWKTJYZATIA' + b'RAZRUCRGTWXWP9SZPFOVAMLISUPQUKHNDMITUJKIB' + ), + + branch_transaction_hash = + TransactionHash( + b'UKGIAYNLALFGJOVUZYJGNIOZSXBBZDXVQLUMHGQE' + b'PZJWYDMGTPJIQXS9GOKXR9WIGWFRWRSKGCJ999999' + ), + + bundle_hash = + BundleHash( + b'ZSATLX9HDENCIARERVLWYHXPQETFL9QKTNC9LUOL' + b'CDXKKW9MYTLZJDXBNOHURUXSYWMGGD9UDGLHCSZO9' + ), + + nonce = + Hash( + b'VRYLDCKEWZJXPQVSWOJVYVBJSCWZQEVKPBG9KGEZ' + b'GPRQFKFSRNBPXCSVQNJINBRNEPIKAXNHOTJFIVYJO' + ), + + trunk_transaction_hash = + TransactionHash( + b'QSTUKDIBYAZIQVEOMFGKQPHAIPBHUPSDQFFKKHRA' + b'ABYMYMQDHMTEWCM9IMZHQMGXOTWNSJYHRNA999999' + ), + + signature_message_fragment = + Fragment( + b'XS9OVIXHIGGR9IYQBHGMFAHPZBWLIBNAQPFMPVYUZDOLLFDJIPZEMIOGVANQJSCU' + b'IPDNNUNAMWEL9OFXXK9NV9UTCRBYTARBJHPQYJYKNAQGMATG9EXQMHGXY9QOHPBA' + b'FEVABDYMCXORXHBMPLEWJYGYFFBWVXAUXHGLTABBKOQMZLFAYWDAKEOMJPJX9TMT' + b'GXIJXZTKRRIPAMYY9UNSPPEGFPJE9NFSJFWKYOFZRMPBXZDNQUEKLRUVPXMCTQRE' + b'ZWICSCVXN9VBLN9DRINRPAZTYJYXPGGRZJLMYXGCLUQNZ9NJGH9GFQPKKVK9N9WR' + b'IJXDNKUMLLJUVIQRGPHEVWTXQHRLRCWQJCHTPASCVLRGPNWSIUKWIBMDJJ9EUTQ9' + b'NXZZEJFWY9LCJJSOEPXWETUBKKVZNUKTLUPEPDBLUWCQGYTOXZ9NZUXHBDOUYQBP' + b'MNECVJ9HGWA9AWU9VHGETWKBU9YZEZGEQKMVTAKPLCZVWKQFXDEFBPKNUCQDSPWA' + b'LMPFTUFGRFDZH9PQHJ9WXZPCDWGMNASVVEUXEGWATM9ZIMCEEXTHCXFLYG9LQAKV' + b'UOGORP9UUWYFTWGZ9OFOGSP9KDNPDSQKEMMISEMWQDVFKCSQXSP9RUMNUQJOBACU' + b'MPIXCGBJLQQGB9GDSMUUUSYWIY9ZNIAIZBJYTAJKJKZIBFPMGDWUEPXSO9HUJRNV' + b'QE9OTVUPKBVNVUBSILVZEDPC9AMEYAIATE9JEIQQWIMGCZXMHOPXPFUTEPJEATJN' + b'QWDFZQ9OGPNBFUHLJDJZQJLXCFEUDPZKVCPEBTNBRYNIIKJHUV9EUFVMB9AHTARQ' + b'DN9TZ9JPAPCEXSYHZPAQLBJMNQ9R9KPWVZLDLFNHYCCQBFVEPMEZSXAB9GOKGOGC' + b'SOVL9XJSAQYSECY9UUNSVQSJB9BZVYRUURNUDMZVBJTMIDQUKQLMVW99XFDWTOPR' + b'UBRPKS9BGEAQPIODAMJAILNCH9UVYVWSDCZXZWLO9XJJZ9FQOL9F9ZJDNGMUGFKJ' + b'PCYURGYBGYRVKPEBKMJPZZGDKZKT9UBFSJEELREWOYDQZVUPVSGPZYIDVOJGNTXC' + b'OFGCHBGVZPQDNRKAQNVJEYKYTKHTFBJRDMKVSHEWADNYIQOAUFXYMZKNJPLXGYFX' + b'DTCVDDBUHBDPG9WLNMWPSCCCGVTIOOLEECXKNVAYNNTDLJMDGDGSKOGWO9UYXTTF' + b'FCRZEDDQNN9ZODTETGMGGUXOYECGNMHGMGXHZSPODIBMBATJJHSPQHDUCZOMWQNI' + b'CUZG9LAMBOQRQQDIPIBMIDCIAJBBBNDUAIEMFCEASHPUJPFPPXNDUVGDNNYBRRTW' + b'SPXGXMCSUXYJSTFIRUIDNEUSVKNIDKIBRQNMEDCYQOMJYTMGRZLYHBUYXCRGSAXR' + b'ZVHTZEAKNAUKJPFGPOGQLTDMSOXR9NVOIAIMCBVWOF9FXAZUKKZKHJEGHFNLUB9B' + b'TGAICGQGAYZRRHSFIDTNIJPHIHCXTHQUSKJRSVAWFUXLBYA99QKMGLHDNUHOPEW9' + b'OFNWPDXXRVZREUIQKSVSDCFIJ99TSGSZ9KU9JGE9VXDVVOLMGNMUGSHUZAOFCIMK' + b'CPEWMG9IHUZAILQCANIUUG9JNEZMT9EONSN9CWWQOTFBEPZRTTJTQFSTQTBERKGE' + b'NGFFIYMZMCFBYNIOBPOFOIYPUMYYPRXEHUJEVVELOPNXAPCYFXQ9ORMSFICDOZTS' + b'GQOMDI9FKEKRIMZTWSIWMYAUSBIN9TPFSMQZCYGVPVWKSFZXPE9BP9ALNWQOVJGM' + b'SCSJSTNUTMUAJUIQTITPPOHG9NKIFRNXSCMDAEW9LSUCTCXITSTZSBYMPOMSMTXP' + b'CEBEOAUJK9STIZRXUORRQBCYJPCNHFKEVY9YBJL9QGLVUCSZKOLHD9BDNKIVJX9T' + b'PPXQVGAXUSQQYGFDWQRZPKZKKWB9ZBFXTUGUGOAQLDTJPQXPUPHNATSGILEQCSQX' + b'X9IAGIVKUW9MVNGKTSCYDMPSVWXCGLXEHWKRPVARKJFWGRYFCATYNZDTRZDGNZAI' + b'OULYHRIPACAZLN9YHOFDSZYIRZJEGDUZBHFFWWQRNOLLWKZZENKOWQQYHGLMBMPF' + b'HE9VHDDTBZYHMKQGZNCSLACYRCGYSFFTZQJUSZGJTZKKLWAEBGCRLXQRADCSFQYZ' + b'G9CM9VLMQZA' + ), + + current_index = 1, + last_index = 3, + tag = Tag(b''), + timestamp = 1483033814, + value = -99, + ), + + # Input #1, Part 2 of 2 + Transaction( + hash_ = + TransactionHash( + b'QSTUKDIBYAZIQVEOMFGKQPHAIPBHUPSDQFFKKHRA' + b'ABYMYMQDHMTEWCM9IMZHQMGXOTWNSJYHRNA999999' + ), + + address = + Address( + b'GMHOSRRGXJLDPVWRWVSRWI9BCIVLUXWKTJYZATIA' + b'RAZRUCRGTWXWP9SZPFOVAMLISUPQUKHNDMITUJKIB' + ), + + branch_transaction_hash = + TransactionHash( + b'UKGIAYNLALFGJOVUZYJGNIOZSXBBZDXVQLUMHGQE' + b'PZJWYDMGTPJIQXS9GOKXR9WIGWFRWRSKGCJ999999' + ), + + bundle_hash = + BundleHash( + b'ZSATLX9HDENCIARERVLWYHXPQETFL9QKTNC9LUOL' + b'CDXKKW9MYTLZJDXBNOHURUXSYWMGGD9UDGLHCSZO9' + ), + + nonce = + Hash( + b'AAKVYZOEZSOXTX9LOLHZYLNAS9CXBLSWVZQAMRGW' + b'YW9GHHMVIOHWBMTXHDBXRTF9DEFFQFQESNVJORNXK' + ), + + trunk_transaction_hash = + TransactionHash( + b'ZYQGVZABMFVLJXHXXJMVAXOXHRJTTQUVDIIQOOXN' + b'NDPQGDFDRIDQMUWJGCQKKLGEUQRBFAJWZBC999999' + ), + + signature_message_fragment = + Fragment( + b'YSNEGUCUHXEEFXPQEABV9ATGQMMXEVGMZWKKAFAVOVGUECOZZJFQDNRBCSXCOTBD' + b'BRUJ9HF9CITXQI9ZQGZFKCXMFZTOYHUTCXDIBIMTBKVXMMTPNKRDRLQESLWFZSQQ' + b'9BCGKVIZAHBWYTNXG9OWOXHAMQECMOVKN9SOEVJBBARPXUXYUQVFPYXWXQQMDIVP' + b'VITRWTNNBY9CYBHXJTZUVIPJJG9WLTNMFVPXGYZCNOGSLGVMS9YXXNSV9AYPXZTA' + b'QJYUNUFBCSZBZNKWCPMVMOGFIDENTOOOCPRDJTNGQRLA9YKMLYZQRO9QQJMCSYVF' + b'YLISFIWQQYMWMHUOEZPATYCEZARLWLAMCZWYWJZVD9WWKYJURTOLITFFRXQUBKST' + b'DG9CKDBLPXTPCIMKEKRGEXJGLRL9ZST9VOLV9NOFZLIMVOZBDZJUQISUWZKOJCRN' + b'YRBRJLCTNPV9QIWQJZDQFVPSTW9BJYWHNRVQTITWJYB9HBUQBXTAGK9BZCHYWYPE' + b'IREDOXCYRW9UXVSLZBBPAFIUEJABMBYKSUPNWVVKAFQJKDAYYRDICTGOTWWDSFLG' + b'BQFZZ9NBEHZHPHVQUYEETIRUDM9V9LBXFUXTUGUMZG9HRBLXCKMMWWMK9VTKVZSA' + b'PRSMJVBLFFDHTYCPDXKBUYYLZDPW9EVXANPZOPBASQUPRNCDGHNUK9NDUQSULUZI' + b'VMIJTPUGMZPCYR9AERBAGUYNGVEHWIIADAAPPMYQOAGBQCXEDTQOGHWHHSWDFZLC' + b'DVLNPYMGDPZWOZURT9OZKDJKFECXSFIALXJDRJWMWMTNUUNVDUCJAZLDRN9ZWLHH' + b'SNXDWULUBNLVRDJZQMKCTRCKULKS9VARFZSRYZCPNH9FHXCAFWKPNGOPOFMYXJLE' + b'LTKUHSZVDQRDJIGQRGOSKYWDCU9EBJMXQDBKTBNQTIZNCALHRNTHKN99WOBQVVEV' + b'HEDTDRKFPGLIWOSPLAAELQQXDCDWPIFED9OEUPYPKHZBOHPQGQGSEKO9BFIQFYZK' + b'YEULWSIBZVSPXBGOJTTYBVIIIPAXGL9ZJNNIELFYAUOUBRDWLJJMSAXHQOYGOWDV' + b'HHPISRZFSHPDLNQDFWRHLWNAJPYM9REAJLZDIAIVLQBFAUJIQKVHJDFPXENI9ZM9' + b'SFNGSQHDFEDC9CQVXAXTQVLWYMVSLEDCOVNSQLSANLVA9TWSY9BHAJKOCGI9YLAB' + b'VROCBJRVXRWBKNUXCAXJIAYWSFRDZHIPQSNBRYNKZAFXHDUENVLHFHYIKH9IANFV' + b'FKWVFJCSEELVTDDUHBPIYNFLTJLINNORIMDEAXMN9LGNGBWVWYWQIPWKBFDKNDOX' + b'WFKGBAMZIUFYA9DXGAL9OQQTJAUUXTINWZSQUTPUKUMOZCGOBKKFBXCVR9AGTAQS' + b'SVGTUBBHSIRHFRSIR9SKSZPXQFG9AOYAHZNQR9AHSEFCKWCJHUTLREDVGBQYVBZR' + b'CZDXFG9PTSAWQOURYKNWYAZNASV9UMUYUMFCQSFDHZD99WUMCORLYTIZMRGNBAY9' + b'UJYJMMRCLJP9XVLXTAZOHNVVYSCOSDHGUOPXIRBJDXJUCJYLQKUJOTNJCPRBDOKV' + b'ZEMIGZRNJOQKFFAXQVGGY9YRJORZCOD9REIIIDDTRQ9TJWTFYRKOPLAFNUUPCHXA' + b'WVPYUQXAFFCTYAESWAFUTQQYZRQVLVZW9OWAAJMPSAEPKWXVEZVTVPQEEBVXNZJP' + b'ZU9JJSIAEPIT9HE99XNAUYOAKRIFQQJQTFIMWEOKLCH9JKCQTGZPEGWORFB9ARNS' + b'DPYKRONBONYOGEVEFXGTMQTQBEMFQWEMIDSGAVEQHVHAPSMTCJ9FMEYBWAQWWJCE' + b'ABUUMMVNDMSBORFLHVIIDOUQHHXQKXTVGRAYTLMECCSVZOZM9JKUWIGGFLMMDGBU' + b'DBIHJFUINVOKSFTOGFCZEMIBSZNGPL9HXWGTNNAKYIMDITCRMSHFR9BDSFGHXQMR' + b'ACZOVUOTSJSKMNHNYIFEOD9CVBWYVVMG9ZDNR9FOIXSZSTIO9GLOLPLMW9RPAJYB' + b'WTCKV9JMSEVGD9ZPEGKXF9XYQMUMJPWTMFZJODFIEYNLI9PWODSPPW9MVJOWZQZU' + b'CIKXCVVXDKWHXV99GOEZ9CMGUH9OWGLLISNZEPSAPEDHVRKKGFFNGBXFLDBQTTQL' + b'WVLUITJQ9JM' + ), + + current_index = 2, + last_index = 3, + tag = Tag(b''), + timestamp = 1483033814, + value = 0, + ), + + # "Change" transaction, Part 1 of 1 + Transaction( + hash_ = + TransactionHash( + b'ZYQGVZABMFVLJXHXXJMVAXOXHRJTTQUVDIIQOOXN' + b'NDPQGDFDRIDQMUWJGCQKKLGEUQRBFAJWZBC999999' + ), + + address = + Address( + b'YOTMYW9YLZQCSLHB9WRSTZDYYYGUUWLVDRHFQFEX' + b'UVOQARTQWZGLBU9DVSRDPCWYWQZHLFHY9NGLPZRAQ' + ), + + branch_transaction_hash = + TransactionHash( + b'QCHKLZZBG9XQMNGCDVXZGDRXIJMFZP9XUGAWNNVP' + b'GXBWB9NVEKEFMUWOEACULFUR9Q9XCWPBRNF999999' + ), + + bundle_hash = + BundleHash( + b'ZSATLX9HDENCIARERVLWYHXPQETFL9QKTNC9LUOL' + b'CDXKKW9MYTLZJDXBNOHURUXSYWMGGD9UDGLHCSZO9' + ), + + nonce = + Hash( + b'TPGXQFUGNEYYFFKPFWJSXKTWEUKUFTRJCQKKXLXL' + b'PSOHBZTGIBFPGLSVRIVYAC9NZMOMZLARFZYCNNRCM' + ), + + trunk_transaction_hash = + TransactionHash( + b'UKGIAYNLALFGJOVUZYJGNIOZSXBBZDXVQLUMHGQE' + b'PZJWYDMGTPJIQXS9GOKXR9WIGWFRWRSKGCJ999999' + ), + + signature_message_fragment = Fragment(b''), + + current_index = 3, + last_index = 3, + tag = Tag(b''), + timestamp = 1483033814, + value = 98, + ), + ]) + + mock_get_bundles = Mock(return_value={ + 'bundles': [bundle], + }) + + send_trytes_response = { + 'trytes': bundle.as_tryte_strings(head_to_tail=True), + } + + mock_send_trytes = Mock(return_value=send_trytes_response) + + with patch( + 'iota.commands.extended.get_bundles.GetBundlesCommand._execute', + mock_get_bundles, + ): + with patch( + 'iota.commands.extended.send_trytes.SendTrytesCommand._execute', + mock_send_trytes, + ): + response = self.command( + depth = 100, + min_weight_magnitude = 18, + transaction = bundle[0].hash, + ) + + self.assertDictEqual(response, send_trytes_response) diff --git a/test/commands/extended/send_transfer_test.py b/test/commands/extended/send_transfer_test.py index ea91e98..e1355e4 100644 --- a/test/commands/extended/send_transfer_test.py +++ b/test/commands/extended/send_transfer_test.py @@ -6,12 +6,15 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import Address, Iota, ProposedTransaction, TryteString +from mock import Mock, patch +from six import binary_type, text_type + +from iota import Address, Bundle, Iota, ProposedTransaction, \ + TransactionTrytes, TryteString +from iota.adapter import MockAdapter from iota.commands.extended.send_transfer import SendTransferCommand from iota.crypto.types import Seed from iota.filters import Trytes -from six import binary_type, text_type -from test import MockAdapter class SendTransferRequestFilterTestCase(BaseFilterTestCase): @@ -574,7 +577,8 @@ class SendTransferCommandTestCase(TestCase): def setUp(self): super(SendTransferCommandTestCase, self).setUp() - self.adapter = MockAdapter() + self.adapter = MockAdapter() + self.command = SendTransferCommand(self.adapter) def test_wireup(self): """ @@ -585,4 +589,90 @@ def test_wireup(self): SendTransferCommand, ) - # :todo: Unit tests. + def test_happy_path(self): + """ + Sending a transfer successfully. + """ + # noinspection SpellCheckingInspection + transaction1 =\ + TransactionTrytes( + b'GYPRVHBEZOOFXSHQBLCYW9ICTCISLHDBNMMVYD9JJHQMPQCTIQAQTJNNNJ9IDXLRCC' + b'OYOXYPCLR9PBEY9ORZIEPPDNTI9CQWYZUOTAVBXPSBOFEQAPFLWXSWUIUSJMSJIIIZ' + b'WIKIRH9GCOEVZFKNXEVCUCIIWZQCQEUVRZOCMEL9AMGXJNMLJCIA9UWGRPPHCEOPTS' + b'VPKPPPCMQXYBHMSODTWUOABPKWFFFQJHCBVYXLHEWPD9YUDFTGNCYAKQKVEZYRBQRB' + b'XIAUX9SVEDUKGMTWQIYXRGSWYRK9SRONVGTW9YGHSZRIXWGPCCUCDRMAXBPDFVHSRY' + b'WHGB9DQSQFQKSNICGPIPTRZINYRXQAFSWSEWIFRMSBMGTNYPRWFSOIIWWT9IDSELM9' + b'JUOOWFNCCSHUSMGNROBFJX9JQ9XT9PKEGQYQAWAFPRVRRVQPUQBHLSNTEFCDKBWRCD' + b'X9EYOBB9KPMTLNNQLADBDLZPRVBCKVCYQEOLARJYAGTBFR9QLPKZBOYWZQOVKCVYRG' + b'YI9ZEFIQRKYXLJBZJDBJDJVQZCGYQMROVHNDBLGNLQODPUXFNTADDVYNZJUVPGB9LV' + b'PJIYLAPBOEHPMRWUIAJXVQOEM9ROEYUOTNLXVVQEYRQWDTQGDLEYFIYNDPRAIXOZEB' + b'CS9P99AZTQQLKEILEVXMSHBIDHLXKUOMMNFKPYHONKEYDCHMUNTTNRYVMMEYHPGASP' + b'ZXASKRUPWQSHDMU9VPS99ZZ9SJJYFUJFFMFORBYDILBXCAVJDPDFHTTTIYOVGLRDYR' + b'TKHXJORJVYRPTDH9ZCPZ9ZADXZFRSFPIQKWLBRNTWJHXTOAUOL9FVGTUMMPYGYICJD' + b'XMOESEVDJWLMCVTJLPIEKBE9JTHDQWV9MRMEWFLPWGJFLUXI9BXPSVWCMUWLZSEWHB' + b'DZKXOLYNOZAPOYLQVZAQMOHGTTQEUAOVKVRRGAHNGPUEKHFVPVCOYSJAWHZU9DRROH' + b'BETBAFTATVAUGOEGCAYUXACLSSHHVYDHMDGJP9AUCLWLNTFEVGQGHQXSKEMVOVSKQE' + b'EWHWZUDTYOBGCURRZSJZLFVQQAAYQO9TRLFFN9HTDQXBSPPJYXMNGLLBHOMNVXNOWE' + b'IDMJVCLLDFHBDONQJCJVLBLCSMDOUQCKKCQJMGTSTHBXPXAMLMSXRIPUBMBAWBFNLH' + b'LUJTRJLDERLZFUBUSMF999XNHLEEXEENQJNOFFPNPQ9PQICHSATPLZVMVIWLRTKYPI' + b'XNFGYWOJSQDAXGFHKZPFLPXQEHCYEAGTIWIJEZTAVLNUMAFWGGLXMBNUQTOFCNLJTC' + b'DMWVVZGVBSEBCPFSM99FLOIDTCLUGPSEDLOKZUAEVBLWNMODGZBWOVQT9DPFOTSKRA' + b'BQAVOQ9RXWBMAKFYNDCZOJGTCIDMQSQQSODKDXTPFLNOKSIZEOY9HFUTLQRXQMEPGO' + b'XQGLLPNSXAUCYPGZMNWMQWSWCKAQYKXJTWINSGPPZG9HLDLEAWUWEVCTVRCBDFOXKU' + b'ROXH9HXXAXVPEJFRSLOGRVGYZASTEBAQNXJJROCYRTDPYFUIQJVDHAKEG9YACV9HCP' + b'JUEUKOYFNWDXCCJBIFQKYOXGRDHVTHEQUMHO999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999RKWEEVD99A99999999A99999999NFDPEEZCWVYLKZGSLCQNOFUSENI' + b'XRHWWTZFBXMPSQHEDFWZULBZFEOMNLRNIDQKDNNIELAOXOVMYEI9PGTKORV9IKTJZQ' + b'UBQAWTKBKZ9NEZHBFIMCLV9TTNJNQZUIJDFPTTCTKBJRHAITVSKUCUEMD9M9SQJ999' + b'999TKORV9IKTJZQUBQAWTKBKZ9NEZHBFIMCLV9TTNJNQZUIJDFPTTCTKBJRHAITVSK' + b'UCUEMD9M9SQJ999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999' + ) + + mock_prepare_transfer = Mock(return_value={ + 'trytes': [transaction1], + }) + + mock_send_trytes = Mock(return_value={ + 'trytes': [transaction1], + }) + + with patch( + 'iota.commands.extended.prepare_transfer.PrepareTransferCommand._execute', + mock_prepare_transfer, + ): + with patch( + 'iota.commands.extended.send_trytes.SendTrytesCommand._execute', + mock_send_trytes, + ): + response = self.command( + depth = 100, + min_weight_magnitude = 18, + seed = Seed.random(), + + transfers = [ + ProposedTransaction( + address = + Address( + b'9999999999999999999999999999999999999999' + b'99999999999999999999999999999999999999999' + ), + + value = 0, + ), + ], + ) + + bundle = response['bundle'] # type: Bundle + self.assertEqual(len(bundle), 1) + self.assertEqual(bundle[0].as_tryte_string(), transaction1) diff --git a/test/commands/extended/send_trytes_test.py b/test/commands/extended/send_trytes_test.py index c7b8b6f..37d6c1b 100644 --- a/test/commands/extended/send_trytes_test.py +++ b/test/commands/extended/send_trytes_test.py @@ -6,11 +6,12 @@ import filters as f from filters.test import BaseFilterTestCase -from iota import BadApiResponse, Iota, TransactionHash, TryteString +from iota import BadApiResponse, Iota, TransactionHash, TransactionTrytes, \ + TryteString +from iota.adapter import MockAdapter from iota.commands.extended.send_trytes import SendTrytesCommand from iota.filters import Trytes from six import text_type, binary_type -from test import MockAdapter class SendTrytesRequestFilterTestCase(BaseFilterTestCase): @@ -35,8 +36,10 @@ def test_pass_happy_path(self): 'depth': 100, 'min_weight_magnitude': 18, - 'trytes': - [TryteString(self.trytes1), TryteString(self.trytes2)], + 'trytes': [ + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), + ], } filter_ = self._filter(request) @@ -69,8 +72,10 @@ def test_pass_compatible_types(self): 'depth': 100, 'min_weight_magnitude': 18, - 'trytes': - [TryteString(self.trytes1), TryteString(self.trytes2)], + 'trytes': [ + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), + ], }, ) @@ -318,6 +323,8 @@ def test_fail_trytes_contents_invalid(self): TryteString(self.trytes1), 2130706433, + + b'9' * (TransactionTrytes.LEN + 1), ], 'depth': 100, @@ -325,12 +332,13 @@ def test_fail_trytes_contents_invalid(self): }, { - 'trytes.0': [f.Required.CODE_EMPTY], - 'trytes.1': [f.Type.CODE_WRONG_TYPE], - 'trytes.2': [f.Type.CODE_WRONG_TYPE], - 'trytes.3': [f.Required.CODE_EMPTY], - 'trytes.4': [Trytes.CODE_NOT_TRYTES], - 'trytes.6': [f.Type.CODE_WRONG_TYPE], + 'trytes.0': [f.Required.CODE_EMPTY], + 'trytes.1': [f.Type.CODE_WRONG_TYPE], + 'trytes.2': [f.Type.CODE_WRONG_TYPE], + 'trytes.3': [f.Required.CODE_EMPTY], + 'trytes.4': [Trytes.CODE_NOT_TRYTES], + 'trytes.6': [f.Type.CODE_WRONG_TYPE], + 'trytes.7': [Trytes.CODE_WRONG_FORMAT], }, ) @@ -394,8 +402,8 @@ def test_happy_path(self): response = self.command( trytes = [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ], depth = 100, @@ -421,8 +429,8 @@ def test_happy_path(self): 'min_weight_magnitude': 18, 'trytes': [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ], }, @@ -430,8 +438,8 @@ def test_happy_path(self): 'command': 'broadcastTransactions', 'trytes': [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ], }, @@ -439,8 +447,8 @@ def test_happy_path(self): 'command': 'storeTransactions', 'trytes': [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ], }, ], @@ -494,8 +502,8 @@ def test_attach_to_tangle_fails(self): with self.assertRaises(BadApiResponse): self.command( trytes = [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ], depth = 100, @@ -521,8 +529,8 @@ def test_attach_to_tangle_fails(self): 'min_weight_magnitude': 18, 'trytes': [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ], }, ], @@ -551,8 +559,8 @@ def test_broadcast_transactions_fails(self): with self.assertRaises(BadApiResponse): self.command( trytes = [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ], depth = 100, @@ -578,8 +586,8 @@ def test_broadcast_transactions_fails(self): 'min_weight_magnitude': 18, 'trytes': [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ], }, @@ -587,8 +595,8 @@ def test_broadcast_transactions_fails(self): 'command': 'broadcastTransactions', 'trytes': [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ], }, ], @@ -624,8 +632,8 @@ def test_store_transactions_fails(self): with self.assertRaises(BadApiResponse): self.command( trytes = [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ], depth = 100, @@ -649,8 +657,8 @@ def test_store_transactions_fails(self): 'min_weight_magnitude': 18, 'trytes': [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ], }, @@ -658,8 +666,8 @@ def test_store_transactions_fails(self): 'command': 'broadcastTransactions', 'trytes': [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ], }, @@ -667,8 +675,8 @@ def test_store_transactions_fails(self): 'command': 'storeTransactions', 'trytes': [ - TryteString(self.trytes1), - TryteString(self.trytes2), + TransactionTrytes(self.trytes1), + TransactionTrytes(self.trytes2), ], }, ], diff --git a/test/transaction_test.py b/test/transaction_test.py index 9b49ec0..8238672 100644 --- a/test/transaction_test.py +++ b/test/transaction_test.py @@ -8,8 +8,8 @@ from mock import patch from iota import Address, Bundle, BundleHash, Fragment, Hash, ProposedBundle, \ - ProposedTransaction, Tag, Transaction, TransactionHash, TryteString, \ - trits_from_int + ProposedTransaction, Tag, Transaction, TransactionHash, TransactionTrytes, \ + TryteString, trits_from_int from iota.crypto.addresses import AddressGenerator from iota.crypto.signing import KeyGenerator from iota.transaction import BundleValidator @@ -1049,49 +1049,50 @@ def test_from_tryte_string(self): Initializing a Transaction object from a TryteString. """ # :see: http://iotasupport.com/news/index.php/2016/12/02/fixing-the-latest-solid-subtangle-milestone-issue/ - trytes = ( - b'GYPRVHBEZOOFXSHQBLCYW9ICTCISLHDBNMMVYD9JJHQMPQCTIQAQTJNNNJ9IDXLRCC' - b'OYOXYPCLR9PBEY9ORZIEPPDNTI9CQWYZUOTAVBXPSBOFEQAPFLWXSWUIUSJMSJIIIZ' - b'WIKIRH9GCOEVZFKNXEVCUCIIWZQCQEUVRZOCMEL9AMGXJNMLJCIA9UWGRPPHCEOPTS' - b'VPKPPPCMQXYBHMSODTWUOABPKWFFFQJHCBVYXLHEWPD9YUDFTGNCYAKQKVEZYRBQRB' - b'XIAUX9SVEDUKGMTWQIYXRGSWYRK9SRONVGTW9YGHSZRIXWGPCCUCDRMAXBPDFVHSRY' - b'WHGB9DQSQFQKSNICGPIPTRZINYRXQAFSWSEWIFRMSBMGTNYPRWFSOIIWWT9IDSELM9' - b'JUOOWFNCCSHUSMGNROBFJX9JQ9XT9PKEGQYQAWAFPRVRRVQPUQBHLSNTEFCDKBWRCD' - b'X9EYOBB9KPMTLNNQLADBDLZPRVBCKVCYQEOLARJYAGTBFR9QLPKZBOYWZQOVKCVYRG' - b'YI9ZEFIQRKYXLJBZJDBJDJVQZCGYQMROVHNDBLGNLQODPUXFNTADDVYNZJUVPGB9LV' - b'PJIYLAPBOEHPMRWUIAJXVQOEM9ROEYUOTNLXVVQEYRQWDTQGDLEYFIYNDPRAIXOZEB' - b'CS9P99AZTQQLKEILEVXMSHBIDHLXKUOMMNFKPYHONKEYDCHMUNTTNRYVMMEYHPGASP' - b'ZXASKRUPWQSHDMU9VPS99ZZ9SJJYFUJFFMFORBYDILBXCAVJDPDFHTTTIYOVGLRDYR' - b'TKHXJORJVYRPTDH9ZCPZ9ZADXZFRSFPIQKWLBRNTWJHXTOAUOL9FVGTUMMPYGYICJD' - b'XMOESEVDJWLMCVTJLPIEKBE9JTHDQWV9MRMEWFLPWGJFLUXI9BXPSVWCMUWLZSEWHB' - b'DZKXOLYNOZAPOYLQVZAQMOHGTTQEUAOVKVRRGAHNGPUEKHFVPVCOYSJAWHZU9DRROH' - b'BETBAFTATVAUGOEGCAYUXACLSSHHVYDHMDGJP9AUCLWLNTFEVGQGHQXSKEMVOVSKQE' - b'EWHWZUDTYOBGCURRZSJZLFVQQAAYQO9TRLFFN9HTDQXBSPPJYXMNGLLBHOMNVXNOWE' - b'IDMJVCLLDFHBDONQJCJVLBLCSMDOUQCKKCQJMGTSTHBXPXAMLMSXRIPUBMBAWBFNLH' - b'LUJTRJLDERLZFUBUSMF999XNHLEEXEENQJNOFFPNPQ9PQICHSATPLZVMVIWLRTKYPI' - b'XNFGYWOJSQDAXGFHKZPFLPXQEHCYEAGTIWIJEZTAVLNUMAFWGGLXMBNUQTOFCNLJTC' - b'DMWVVZGVBSEBCPFSM99FLOIDTCLUGPSEDLOKZUAEVBLWNMODGZBWOVQT9DPFOTSKRA' - b'BQAVOQ9RXWBMAKFYNDCZOJGTCIDMQSQQSODKDXTPFLNOKSIZEOY9HFUTLQRXQMEPGO' - b'XQGLLPNSXAUCYPGZMNWMQWSWCKAQYKXJTWINSGPPZG9HLDLEAWUWEVCTVRCBDFOXKU' - b'ROXH9HXXAXVPEJFRSLOGRVGYZASTEBAQNXJJROCYRTDPYFUIQJVDHAKEG9YACV9HCP' - b'JUEUKOYFNWDXCCJBIFQKYOXGRDHVTHEQUMHO999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999RKWEEVD99A99999999A99999999NFDPEEZCWVYLKZGSLCQNOFUSENI' - b'XRHWWTZFBXMPSQHEDFWZULBZFEOMNLRNIDQKDNNIELAOXOVMYEI9PGTKORV9IKTJZQ' - b'UBQAWTKBKZ9NEZHBFIMCLV9TTNJNQZUIJDFPTTCTKBJRHAITVSKUCUEMD9M9SQJ999' - b'999TKORV9IKTJZQUBQAWTKBKZ9NEZHBFIMCLV9TTNJNQZUIJDFPTTCTKBJRHAITVSK' - b'UCUEMD9M9SQJ999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999' - ) + trytes =\ + TransactionTrytes( + b'GYPRVHBEZOOFXSHQBLCYW9ICTCISLHDBNMMVYD9JJHQMPQCTIQAQTJNNNJ9IDXLRCC' + b'OYOXYPCLR9PBEY9ORZIEPPDNTI9CQWYZUOTAVBXPSBOFEQAPFLWXSWUIUSJMSJIIIZ' + b'WIKIRH9GCOEVZFKNXEVCUCIIWZQCQEUVRZOCMEL9AMGXJNMLJCIA9UWGRPPHCEOPTS' + b'VPKPPPCMQXYBHMSODTWUOABPKWFFFQJHCBVYXLHEWPD9YUDFTGNCYAKQKVEZYRBQRB' + b'XIAUX9SVEDUKGMTWQIYXRGSWYRK9SRONVGTW9YGHSZRIXWGPCCUCDRMAXBPDFVHSRY' + b'WHGB9DQSQFQKSNICGPIPTRZINYRXQAFSWSEWIFRMSBMGTNYPRWFSOIIWWT9IDSELM9' + b'JUOOWFNCCSHUSMGNROBFJX9JQ9XT9PKEGQYQAWAFPRVRRVQPUQBHLSNTEFCDKBWRCD' + b'X9EYOBB9KPMTLNNQLADBDLZPRVBCKVCYQEOLARJYAGTBFR9QLPKZBOYWZQOVKCVYRG' + b'YI9ZEFIQRKYXLJBZJDBJDJVQZCGYQMROVHNDBLGNLQODPUXFNTADDVYNZJUVPGB9LV' + b'PJIYLAPBOEHPMRWUIAJXVQOEM9ROEYUOTNLXVVQEYRQWDTQGDLEYFIYNDPRAIXOZEB' + b'CS9P99AZTQQLKEILEVXMSHBIDHLXKUOMMNFKPYHONKEYDCHMUNTTNRYVMMEYHPGASP' + b'ZXASKRUPWQSHDMU9VPS99ZZ9SJJYFUJFFMFORBYDILBXCAVJDPDFHTTTIYOVGLRDYR' + b'TKHXJORJVYRPTDH9ZCPZ9ZADXZFRSFPIQKWLBRNTWJHXTOAUOL9FVGTUMMPYGYICJD' + b'XMOESEVDJWLMCVTJLPIEKBE9JTHDQWV9MRMEWFLPWGJFLUXI9BXPSVWCMUWLZSEWHB' + b'DZKXOLYNOZAPOYLQVZAQMOHGTTQEUAOVKVRRGAHNGPUEKHFVPVCOYSJAWHZU9DRROH' + b'BETBAFTATVAUGOEGCAYUXACLSSHHVYDHMDGJP9AUCLWLNTFEVGQGHQXSKEMVOVSKQE' + b'EWHWZUDTYOBGCURRZSJZLFVQQAAYQO9TRLFFN9HTDQXBSPPJYXMNGLLBHOMNVXNOWE' + b'IDMJVCLLDFHBDONQJCJVLBLCSMDOUQCKKCQJMGTSTHBXPXAMLMSXRIPUBMBAWBFNLH' + b'LUJTRJLDERLZFUBUSMF999XNHLEEXEENQJNOFFPNPQ9PQICHSATPLZVMVIWLRTKYPI' + b'XNFGYWOJSQDAXGFHKZPFLPXQEHCYEAGTIWIJEZTAVLNUMAFWGGLXMBNUQTOFCNLJTC' + b'DMWVVZGVBSEBCPFSM99FLOIDTCLUGPSEDLOKZUAEVBLWNMODGZBWOVQT9DPFOTSKRA' + b'BQAVOQ9RXWBMAKFYNDCZOJGTCIDMQSQQSODKDXTPFLNOKSIZEOY9HFUTLQRXQMEPGO' + b'XQGLLPNSXAUCYPGZMNWMQWSWCKAQYKXJTWINSGPPZG9HLDLEAWUWEVCTVRCBDFOXKU' + b'ROXH9HXXAXVPEJFRSLOGRVGYZASTEBAQNXJJROCYRTDPYFUIQJVDHAKEG9YACV9HCP' + b'JUEUKOYFNWDXCCJBIFQKYOXGRDHVTHEQUMHO999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999RKWEEVD99A99999999A99999999NFDPEEZCWVYLKZGSLCQNOFUSENI' + b'XRHWWTZFBXMPSQHEDFWZULBZFEOMNLRNIDQKDNNIELAOXOVMYEI9PGTKORV9IKTJZQ' + b'UBQAWTKBKZ9NEZHBFIMCLV9TTNJNQZUIJDFPTTCTKBJRHAITVSKUCUEMD9M9SQJ999' + b'999TKORV9IKTJZQUBQAWTKBKZ9NEZHBFIMCLV9TTNJNQZUIJDFPTTCTKBJRHAITVSK' + b'UCUEMD9M9SQJ999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999' + ) transaction = Transaction.from_tryte_string(trytes) @@ -1198,22 +1199,6 @@ def test_from_tryte_string(self): ), ) - def test_from_tryte_string_error_too_short(self): - """ - Attempting to create a Transaction from a TryteString that is too - short. - """ - # :todo: Implement test. - self.skipTest('Not implemented yet.') - - def test_from_tryte_string_error_too_long(self): - """ - Attempting to create a Transaction from a TryteString that is too - long. - """ - # :todo: Implement test. - self.skipTest('Not implemented yet.') - # noinspection SpellCheckingInspection def test_as_tryte_string(self): """ @@ -1304,45 +1289,47 @@ def test_as_tryte_string(self): self.assertEqual( transaction.as_tryte_string(), - b'GYPRVHBEZOOFXSHQBLCYW9ICTCISLHDBNMMVYD9JJHQMPQCTIQAQTJNNNJ9IDXLRCC' - b'OYOXYPCLR9PBEY9ORZIEPPDNTI9CQWYZUOTAVBXPSBOFEQAPFLWXSWUIUSJMSJIIIZ' - b'WIKIRH9GCOEVZFKNXEVCUCIIWZQCQEUVRZOCMEL9AMGXJNMLJCIA9UWGRPPHCEOPTS' - b'VPKPPPCMQXYBHMSODTWUOABPKWFFFQJHCBVYXLHEWPD9YUDFTGNCYAKQKVEZYRBQRB' - b'XIAUX9SVEDUKGMTWQIYXRGSWYRK9SRONVGTW9YGHSZRIXWGPCCUCDRMAXBPDFVHSRY' - b'WHGB9DQSQFQKSNICGPIPTRZINYRXQAFSWSEWIFRMSBMGTNYPRWFSOIIWWT9IDSELM9' - b'JUOOWFNCCSHUSMGNROBFJX9JQ9XT9PKEGQYQAWAFPRVRRVQPUQBHLSNTEFCDKBWRCD' - b'X9EYOBB9KPMTLNNQLADBDLZPRVBCKVCYQEOLARJYAGTBFR9QLPKZBOYWZQOVKCVYRG' - b'YI9ZEFIQRKYXLJBZJDBJDJVQZCGYQMROVHNDBLGNLQODPUXFNTADDVYNZJUVPGB9LV' - b'PJIYLAPBOEHPMRWUIAJXVQOEM9ROEYUOTNLXVVQEYRQWDTQGDLEYFIYNDPRAIXOZEB' - b'CS9P99AZTQQLKEILEVXMSHBIDHLXKUOMMNFKPYHONKEYDCHMUNTTNRYVMMEYHPGASP' - b'ZXASKRUPWQSHDMU9VPS99ZZ9SJJYFUJFFMFORBYDILBXCAVJDPDFHTTTIYOVGLRDYR' - b'TKHXJORJVYRPTDH9ZCPZ9ZADXZFRSFPIQKWLBRNTWJHXTOAUOL9FVGTUMMPYGYICJD' - b'XMOESEVDJWLMCVTJLPIEKBE9JTHDQWV9MRMEWFLPWGJFLUXI9BXPSVWCMUWLZSEWHB' - b'DZKXOLYNOZAPOYLQVZAQMOHGTTQEUAOVKVRRGAHNGPUEKHFVPVCOYSJAWHZU9DRROH' - b'BETBAFTATVAUGOEGCAYUXACLSSHHVYDHMDGJP9AUCLWLNTFEVGQGHQXSKEMVOVSKQE' - b'EWHWZUDTYOBGCURRZSJZLFVQQAAYQO9TRLFFN9HTDQXBSPPJYXMNGLLBHOMNVXNOWE' - b'IDMJVCLLDFHBDONQJCJVLBLCSMDOUQCKKCQJMGTSTHBXPXAMLMSXRIPUBMBAWBFNLH' - b'LUJTRJLDERLZFUBUSMF999XNHLEEXEENQJNOFFPNPQ9PQICHSATPLZVMVIWLRTKYPI' - b'XNFGYWOJSQDAXGFHKZPFLPXQEHCYEAGTIWIJEZTAVLNUMAFWGGLXMBNUQTOFCNLJTC' - b'DMWVVZGVBSEBCPFSM99FLOIDTCLUGPSEDLOKZUAEVBLWNMODGZBWOVQT9DPFOTSKRA' - b'BQAVOQ9RXWBMAKFYNDCZOJGTCIDMQSQQSODKDXTPFLNOKSIZEOY9HFUTLQRXQMEPGO' - b'XQGLLPNSXAUCYPGZMNWMQWSWCKAQYKXJTWINSGPPZG9HLDLEAWUWEVCTVRCBDFOXKU' - b'ROXH9HXXAXVPEJFRSLOGRVGYZASTEBAQNXJJROCYRTDPYFUIQJVDHAKEG9YACV9HCP' - b'JUEUKOYFNWDXCCJBIFQKYOXGRDHVTHEQUMHO999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999999999999999999999999999999999999' - b'999999999999RKWEEVD99A99999999A99999999NFDPEEZCWVYLKZGSLCQNOFUSENI' - b'XRHWWTZFBXMPSQHEDFWZULBZFEOMNLRNIDQKDNNIELAOXOVMYEI9PGTKORV9IKTJZQ' - b'UBQAWTKBKZ9NEZHBFIMCLV9TTNJNQZUIJDFPTTCTKBJRHAITVSKUCUEMD9M9SQJ999' - b'999TKORV9IKTJZQUBQAWTKBKZ9NEZHBFIMCLV9TTNJNQZUIJDFPTTCTKBJRHAITVSK' - b'UCUEMD9M9SQJ999999999999999999999999999999999999999999999999999999' - b'999999999999999999999999999999999', + TransactionTrytes( + b'GYPRVHBEZOOFXSHQBLCYW9ICTCISLHDBNMMVYD9JJHQMPQCTIQAQTJNNNJ9IDXLRCC' + b'OYOXYPCLR9PBEY9ORZIEPPDNTI9CQWYZUOTAVBXPSBOFEQAPFLWXSWUIUSJMSJIIIZ' + b'WIKIRH9GCOEVZFKNXEVCUCIIWZQCQEUVRZOCMEL9AMGXJNMLJCIA9UWGRPPHCEOPTS' + b'VPKPPPCMQXYBHMSODTWUOABPKWFFFQJHCBVYXLHEWPD9YUDFTGNCYAKQKVEZYRBQRB' + b'XIAUX9SVEDUKGMTWQIYXRGSWYRK9SRONVGTW9YGHSZRIXWGPCCUCDRMAXBPDFVHSRY' + b'WHGB9DQSQFQKSNICGPIPTRZINYRXQAFSWSEWIFRMSBMGTNYPRWFSOIIWWT9IDSELM9' + b'JUOOWFNCCSHUSMGNROBFJX9JQ9XT9PKEGQYQAWAFPRVRRVQPUQBHLSNTEFCDKBWRCD' + b'X9EYOBB9KPMTLNNQLADBDLZPRVBCKVCYQEOLARJYAGTBFR9QLPKZBOYWZQOVKCVYRG' + b'YI9ZEFIQRKYXLJBZJDBJDJVQZCGYQMROVHNDBLGNLQODPUXFNTADDVYNZJUVPGB9LV' + b'PJIYLAPBOEHPMRWUIAJXVQOEM9ROEYUOTNLXVVQEYRQWDTQGDLEYFIYNDPRAIXOZEB' + b'CS9P99AZTQQLKEILEVXMSHBIDHLXKUOMMNFKPYHONKEYDCHMUNTTNRYVMMEYHPGASP' + b'ZXASKRUPWQSHDMU9VPS99ZZ9SJJYFUJFFMFORBYDILBXCAVJDPDFHTTTIYOVGLRDYR' + b'TKHXJORJVYRPTDH9ZCPZ9ZADXZFRSFPIQKWLBRNTWJHXTOAUOL9FVGTUMMPYGYICJD' + b'XMOESEVDJWLMCVTJLPIEKBE9JTHDQWV9MRMEWFLPWGJFLUXI9BXPSVWCMUWLZSEWHB' + b'DZKXOLYNOZAPOYLQVZAQMOHGTTQEUAOVKVRRGAHNGPUEKHFVPVCOYSJAWHZU9DRROH' + b'BETBAFTATVAUGOEGCAYUXACLSSHHVYDHMDGJP9AUCLWLNTFEVGQGHQXSKEMVOVSKQE' + b'EWHWZUDTYOBGCURRZSJZLFVQQAAYQO9TRLFFN9HTDQXBSPPJYXMNGLLBHOMNVXNOWE' + b'IDMJVCLLDFHBDONQJCJVLBLCSMDOUQCKKCQJMGTSTHBXPXAMLMSXRIPUBMBAWBFNLH' + b'LUJTRJLDERLZFUBUSMF999XNHLEEXEENQJNOFFPNPQ9PQICHSATPLZVMVIWLRTKYPI' + b'XNFGYWOJSQDAXGFHKZPFLPXQEHCYEAGTIWIJEZTAVLNUMAFWGGLXMBNUQTOFCNLJTC' + b'DMWVVZGVBSEBCPFSM99FLOIDTCLUGPSEDLOKZUAEVBLWNMODGZBWOVQT9DPFOTSKRA' + b'BQAVOQ9RXWBMAKFYNDCZOJGTCIDMQSQQSODKDXTPFLNOKSIZEOY9HFUTLQRXQMEPGO' + b'XQGLLPNSXAUCYPGZMNWMQWSWCKAQYKXJTWINSGPPZG9HLDLEAWUWEVCTVRCBDFOXKU' + b'ROXH9HXXAXVPEJFRSLOGRVGYZASTEBAQNXJJROCYRTDPYFUIQJVDHAKEG9YACV9HCP' + b'JUEUKOYFNWDXCCJBIFQKYOXGRDHVTHEQUMHO999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999999999999999999999999999999999999' + b'999999999999RKWEEVD99A99999999A99999999NFDPEEZCWVYLKZGSLCQNOFUSENI' + b'XRHWWTZFBXMPSQHEDFWZULBZFEOMNLRNIDQKDNNIELAOXOVMYEI9PGTKORV9IKTJZQ' + b'UBQAWTKBKZ9NEZHBFIMCLV9TTNJNQZUIJDFPTTCTKBJRHAITVSKUCUEMD9M9SQJ999' + b'999TKORV9IKTJZQUBQAWTKBKZ9NEZHBFIMCLV9TTNJNQZUIJDFPTTCTKBJRHAITVSK' + b'UCUEMD9M9SQJ999999999999999999999999999999999999999999999999999999' + b'999999999999999999999999999999999', + ), )