def setUp(self): super(GetAccountDataCommandTestCase, self).setUp() self.adapter = MockAdapter() self.command = GetAccountDataCommand(self.adapter) # Define some tryte sequences we can re-use between tests. self.addy1 =\ Address( b'TESTVALUEONE9DONTUSEINPRODUCTION99999YDZ' b'E9TAFAJGJA9CECKDAEPHBICDR9LHFCOFRBQDHC9IG', key_index = 0, ) self.addy2 =\ Address( b'TESTVALUETWO9DONTUSEINPRODUCTION99999TES' b'GINEIDLEEHRAOGEBMDLENFDAFCHEIHZ9EBZDD9YHL', key_index = 1, ) self.hash1 =\ TransactionHash( b'TESTVALUE9DONTUSEINPRODUCTION99999O99IDB' b'MBPAPDXBSDWAMHV9DASEGCOGHBV9VAF9UGRHFDPFJ' ) self.hash2 =\ TransactionHash( b'TESTVALUE9DONTUSEINPRODUCTION99999OCNCHC' b'TEPBHEPBJEWFXERHSCQCH9TAAANDBBCCHCIDEAVBV' )
def test_pass_approvees_only(self): """ The request only includes approvees. """ request = { 'approvees': [ TransactionHash(self.trytes1), TransactionHash(self.trytes3), ], } filter_ = self._filter(request) self.assertFilterPasses(filter_) self.assertDictEqual( filter_.cleaned_data, { 'approvees': [ text_type(TransactionHash(self.trytes1)), text_type(TransactionHash(self.trytes3)), ], # Null criteria are not included in the request. # https://github.com/iotaledger/iota.lib.py/issues/96 # 'addresses': [], # 'bundles': [], # 'tags': [], }, )
def test_search_results(self): """ The incoming response contains lots of hashes. """ filter_ = self._filter({ 'hashes': [ 'RVORZ9SIIP9RCYMREUIXXVPQIPHVCNPQ9HZWYKFW' 'YWZRE9JQKG9REPKIASHUUECPSQO9JT9XNMVKWYGVA', 'ZJVYUGTDRPDYFGFXMKOTV9ZWSGFK9CFPXTITQLQN' 'LPPG9YNAARMKNKYQO9GSCSBIOTGMLJUFLZWSY9999', ], 'duration': 42, }) self.assertFilterPasses(filter_) self.assertDictEqual( filter_.cleaned_data, { 'hashes': [ TransactionHash( b'RVORZ9SIIP9RCYMREUIXXVPQIPHVCNPQ9HZWYKFW' b'YWZRE9JQKG9REPKIASHUUECPSQO9JT9XNMVKWYGVA', ), TransactionHash( b'ZJVYUGTDRPDYFGFXMKOTV9ZWSGFK9CFPXTITQLQN' b'LPPG9YNAARMKNKYQO9GSCSBIOTGMLJUFLZWSY9999', ), ], 'duration': 42, }, )
def test_pass_all_parameters(self): """ The request contains valid values for all parameters. """ # Raw trytes are extracted to match the IRI's JSON protocol. request = { 'bundles': [ text_type(BundleHash(self.trytes1)), text_type(BundleHash(self.trytes2)), ], 'addresses': [ text_type(Address(self.trytes1)), text_type(Address(self.trytes2)), ], 'tags': [ text_type(Tag(self.trytes1)), text_type(Tag(self.trytes3)), ], 'approvees': [ text_type(TransactionHash(self.trytes1)), text_type(TransactionHash(self.trytes3)), ], } filter_ = self._filter(request) self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, request)
def test_fail_trytes_contents_invalid(self): """ ``trytes`` is an array, but it contains invalid values. """ self.assertFilterErrors( { 'trytes': [ b'', True, None, b'not valid trytes', # This is actually valid; I just added it to make sure the # filter isn't cheating! TryteString(self.trytes2), 2130706433, b'9' * (TransactionTrytes.LEN + 1), ], 'branchTransaction': TransactionHash(self.txn_id), 'minWeightMagnitude': 13, 'trunkTransaction': TransactionHash(self.txn_id), }, { 'trytes.0': [f.NotEmpty.CODE_EMPTY], 'trytes.1': [f.Type.CODE_WRONG_TYPE], 'trytes.2': [f.Required.CODE_EMPTY], 'trytes.3': [Trytes.CODE_NOT_TRYTES], 'trytes.5': [f.Type.CODE_WRONG_TYPE], 'trytes.6': [Trytes.CODE_WRONG_FORMAT], }, )
def test_fail_tips_wrong_type(self): """ ``tips`` is not an array. """ self.assertFilterErrors( { 'tips': TransactionHash(self.trytes2), 'transactions': [TransactionHash(self.trytes1)], }, { 'tips': [f.Type.CODE_WRONG_TYPE], }, )
def test_pass_compatible_types(self): """ Incoming values can be converted into the expected types. """ filter_ = self._filter({ # Any value that can be converted into an ASCII representation of # a TransactionHash is valid here. 'trunkTransaction': TransactionHash(self.txn_id), 'branchTransaction': bytearray(self.txn_id.encode('ascii')), 'trytes': [ # ``trytes`` can contain any value that can be converted into a # TryteString. binary_type(TransactionTrytes(self.trytes1)), # This is probably wrong (s/b :py:class:`TransactionTrytes`), # but technically it's valid. TransactionHash( b'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHDWCTCEAKDCDFD9DSCSA', ), ], # This still has to be an int, however. 'minWeightMagnitude': 30, }) self.assertFilterPasses(filter_) self.assertDictEqual( filter_.cleaned_data, # After running through the filter, all of the values have been # converted to the correct types. { 'trunkTransaction': self.txn_id, 'branchTransaction': self.txn_id, 'minWeightMagnitude': 30, 'trytes': [ text_type(TransactionTrytes(self.trytes1)), text_type( TransactionTrytes( b'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHD' b'WCTCEAKDCDFD9DSCSA99999999999999999999999', )), ], })
def test_fail_min_weight_magnitude_null(self): """ ``minWeightMagnitude`` is null. """ self.assertFilterErrors( { 'minWeightMagnitude': None, 'branchTransaction': TransactionHash(self.txn_id), 'trunkTransaction': TransactionHash(self.txn_id), 'trytes': [TryteString(self.trytes1)], }, { 'minWeightMagnitude': [f.Required.CODE_EMPTY], }, )
def test_fail_min_weight_magnitude_too_small(self): """ ``minWeightMagnitude`` is less than 1. """ self.assertFilterErrors( { 'minWeightMagnitude': 0, 'branchTransaction': TransactionHash(self.txn_id), 'trunkTransaction': TransactionHash(self.txn_id), 'trytes': [TryteString(self.trytes1)], }, { 'minWeightMagnitude': [f.Min.CODE_TOO_SMALL], }, )
def test_fail_trytes_null(self): """ ``trytes`` is null. """ self.assertFilterErrors( { 'trytes': None, 'branchTransaction': TransactionHash(self.txn_id), 'minWeightMagnitude': 13, 'trunkTransaction': TransactionHash(self.txn_id), }, { 'trytes': [f.Required.CODE_EMPTY], }, )
def test_non_tail_transaction(self): """ Trying to get a bundle for a non-tail transaction. This is not valid; you have to start with a tail transaction. """ self.adapter.seed_response('getTrytes', { 'trytes': [ b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999999999999999999999999999999999999999999999999999999999999' b'999999999WUQXEGBVIECGIWO9IGSYKWWPYCIVUJJGSJPWGIAFJPYSF9NSQOHWAHS9P' b'9PWQHOBXNNQIF9IRHVQXKPZW999999999999999999999999999999999999999999' b'999999999999HNLFMVD99A99999999A99999999PDQWLVVDPUU9VIBODGMRIAZPGQX' b'DOGSEXIHKIBWSLDAWUKZCZMK9Z9YZSPCKBDJSVDPRQLJSTKUMTNVSXBGUEHHGAIWWQ' b'BCJZHZAQOWZMAIDAFUZBVMUVPWQJLUGGQKNKLMGTWXXNZKUCBJLEDAMYVRGABAWBY9' b'999MYIYBTGIOQYYZFJBLIAWMPSZEFFTXUZPCDIXSLLQDQSFYGQSQOGSPKCZNLVSZ9L' b'MCUWVNGEN9EJEW9999XZUIENOTTBKJMDPRXWGQYG9PWGTXUO9AXMP9FLMDRMADLRPW' b'CZCJBROYCDRJMYU9HDYJM9NDBFUPIZVTR' ], }) with self.assertRaises(BadApiResponse): self.command( transaction = TransactionHash( b'FSEWUNJOEGNUI9QOCRFMYSIFAZLJHKZBPQZZYFG9' b'ORYCRDX9TOMJPFCRB9R9KPUUGFPVOWYXFIWEW9999' ), )
def test_fail_min_weight_magnitude_string(self): """ ``minWeightMagnitude`` is a string. """ self.assertFilterErrors( { # For want of an int cast, the transaction was lost. 'minWeightMagnitude': '20', 'branchTransaction': TransactionHash(self.txn_id), 'trunkTransaction': TransactionHash(self.txn_id), 'trytes': [TryteString(self.trytes1)], }, { 'minWeightMagnitude': [f.Type.CODE_WRONG_TYPE], }, )
def test_fail_tips_contents_invalid(self): """ ``tips`` contains invalid values. """ self.assertFilterErrors( { 'tips': [ b'', True, None, b'not valid trytes', # This is actually valid; I just added it to make sure the # filter isn't cheating! TryteString(self.trytes1), 2130706433, b'9' * 82, ], 'transactions': [TransactionHash(self.trytes1)], }, { 'tips.0': [f.Required.CODE_EMPTY], 'tips.1': [f.Type.CODE_WRONG_TYPE], 'tips.2': [f.Required.CODE_EMPTY], 'tips.3': [Trytes.CODE_NOT_TRYTES], 'tips.5': [f.Type.CODE_WRONG_TYPE], 'tips.6': [Trytes.CODE_WRONG_FORMAT], }, )
def test_fail_min_weight_magnitude_float(self): """ ``minWeightMagnitude`` is a float. """ self.assertFilterErrors( { # I don't care if the fpart is empty; it's still not an int! 'minWeightMagnitude': 20.0, 'branchTransaction': TransactionHash(self.txn_id), 'trunkTransaction': TransactionHash(self.txn_id), 'trytes': [TryteString(self.trytes1)], }, { 'minWeightMagnitude': [f.Type.CODE_WRONG_TYPE], }, )
def test_pass_compatible_types(self): """ The request contains values that can be converted to the expected types. """ filter_ = self._filter({ 'bundles': [ self.trytes1.encode('ascii'), BundleHash(self.trytes2), ], 'addresses': [ self.trytes1.encode('ascii'), Address(self.trytes2), ], 'tags': [ self.trytes1.encode('ascii'), Tag(self.trytes3), ], 'approvees': [ self.trytes1.encode('ascii'), TransactionHash(self.trytes3), ], }) self.assertFilterPasses(filter_) self.assertDictEqual( filter_.cleaned_data, { # Raw trytes are extracted to match the IRI's JSON protocol. 'bundles': [ text_type(BundleHash(self.trytes1)), text_type(BundleHash(self.trytes2)), ], 'addresses': [ text_type(Address(self.trytes1)), text_type(Address(self.trytes2)), ], 'tags': [ text_type(Tag(self.trytes1)), text_type(Tag(self.trytes3)), ], 'approvees': [ text_type(TransactionHash(self.trytes1)), text_type(TransactionHash(self.trytes3)), ], }, )
def test_fail_trytes_wrong_type(self): """ ``trytes`` is not an array. """ self.assertFilterErrors( { # You have to specify an array, even if you only want to attach # a single tryte sequence. 'trytes': TryteString(self.trytes1), 'branchTransaction': TransactionHash(self.txn_id), 'minWeightMagnitude': 13, 'trunkTransaction': TransactionHash(self.txn_id), }, { 'trytes': [f.Type.CODE_WRONG_TYPE], }, )
def test_init_error_too_long(self): """ Attempting to create a transaction hash longer than 81 trytes. """ with self.assertRaises(ValueError): # noinspection SpellCheckingInspection TransactionHash(b'JVMTDGDPDFYHMZPMWEKKANBQSLSDTIIHAYQUMZOK' b'HXXXGJHJDQPOMDOMNRDKYCZRUFZROZDADTHZC99999')
def test_fail_trytes_empty(self): """ ``trytes`` is an array, but it's empty. """ self.assertFilterErrors( { # Ok, you got the list part down, but you have to put something # inside it. 'trytes': [], 'branchTransaction': TransactionHash(self.txn_id), 'minWeightMagnitude': 13, 'trunkTransaction': TransactionHash(self.txn_id), }, { 'trytes': [f.Required.CODE_EMPTY], }, )
def test_fail_unexpected_parameters(self): """ The incoming request contains unexpected parameters. """ self.assertFilterErrors( { 'branchTransaction': TransactionHash(self.txn_id), 'minWeightMagnitude': 20, 'trunkTransaction': TransactionHash(self.txn_id), 'trytes': [TryteString(self.trytes1)], # Hey, how'd that get in there? 'foo': 'bar', }, { 'foo': [f.FilterMapper.CODE_EXTRA_KEY], }, )
def test_fail_approvees_wrong_type(self): """ ``approvees`` is not an array. """ self.assertFilterErrors( { 'approvees': TransactionHash(self.trytes1), }, { 'approvees': [f.Type.CODE_WRONG_TYPE], }, )
def test_start(self): """ Using ``start`` to offset the key range. """ self.adapter.seed_response('getBalances', { 'balances': [86], }) # ``getInputs`` uses ``findTransactions`` to identify unused # addresses. # noinspection SpellCheckingInspection self.adapter.seed_response( 'findTransactions', { 'hashes': [ TransactionHash( b'TESTVALUE9DONTUSEINPRODUCTION99999YFXGOD' b'GISBJAX9PDJIRDMDV9DCRDCAEG9FN9KECCBDDFZ9H'), ], }) self.adapter.seed_response('findTransactions', { 'hashes': [], }) # To keep the unit test nice and speedy, we will mock the address # generator. We already have plenty of unit tests for that # functionality, so we can get away with mocking it here. # noinspection PyUnusedLocal def mock_address_generator(ag, start, step=1): # If ``start`` has the wrong value, return garbage to make the # test asplode. for addy in [None, self.addy1, self.addy2][start::step]: yield addy # When ``stop`` is None, the command uses a generator internally. with mock.patch( 'iota_async.crypto.addresses.AddressGenerator.create_iterator', mock_address_generator, ): response = self.command( seed=Seed.random(), start=1, ) self.assertEqual(response['totalBalance'], 86) self.assertEqual(len(response['inputs']), 1) input0 = response['inputs'][0] self.assertIsInstance(input0, Address) self.assertEqual(input0, self.addy1) self.assertEqual(input0.balance, 86) self.assertEqual(input0.key_index, 1)
def setUp(self): super(PromoteTransactionCommandTestCase, self).setUp() self.adapter = MockAdapter() self.command = PromoteTransactionCommand(self.adapter) self.trytes1 = b'RBTC9D9DCDQAEASBYBCCKBFA' self.trytes2 =\ b'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHDWCTCEAKDCDFD9DSCSA' self.hash1 = TransactionHash( b'TESTVALUE9DONTUSEINPRODUCTION99999TBPDM9' b'ADFAWCKCSFUALFGETFIFG9UHIEFE9AYESEHDUBDDF')
def test_pass_happy_path_with_reference(self): """ Request is valid with reference. """ request = { 'depth': 100, 'reference': TransactionHash(self.trytes1), } filter_ = self._filter(request) self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, request)
def test_pass_happy_path(self): """ The incoming request is valid. """ request = { 'trunkTransaction': text_type(TransactionHash(self.txn_id)), 'branchTransaction': text_type(TransactionHash(self.txn_id)), 'minWeightMagnitude': 20, 'trytes': [ # Raw trytes are extracted to match the IRI's JSON protocol. text_type(TransactionTrytes(self.trytes1)), text_type(TransactionTrytes(self.trytes2)), ], } filter_ = self._filter(request) self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, request)
def test_pass_happy_path(self): """ Typical ``getTransactionsToApprove`` response. """ response = { 'trunkTransaction': 'TKGDZ9GEI9CPNQGHEATIISAKYPPPSXVCXBSR9EIW' 'CTHHSSEQCD9YLDPEXYERCNJVASRGWMAVKFQTC9999', 'branchTransaction': 'TKGDZ9GEI9CPNQGHEATIISAKYPPPSXVCXBSR9EIW' 'CTHHSSEQCD9YLDPEXYERCNJVASRGWMAVKFQTC9999', 'duration': 936, } filter_ = self._filter(response) self.assertFilterPasses(filter_) self.assertDictEqual( filter_.cleaned_data, { 'trunkTransaction': TransactionHash( b'TKGDZ9GEI9CPNQGHEATIISAKYPPPSXVCXBSR9EIW' b'CTHHSSEQCD9YLDPEXYERCNJVASRGWMAVKFQTC9999' ), 'branchTransaction': TransactionHash( b'TKGDZ9GEI9CPNQGHEATIISAKYPPPSXVCXBSR9EIW' b'CTHHSSEQCD9YLDPEXYERCNJVASRGWMAVKFQTC9999' ), 'duration': 936, }, )
def test_fail_min_weight_magnitude_too_small(self): """ ``minWeightMagnitude`` is < 1. """ self.assertFilterErrors( { 'minWeightMagnitude': 0, 'depth': 100, 'transaction': TransactionHash(self.trytes1), }, { 'minWeightMagnitude': [f.Min.CODE_TOO_SMALL], }, )
def test_fail_hashes_wrong_type(self): """ ``hashes`` is not an array. """ self.assertFilterErrors( { # ``hashes`` must be an array, even if we're only querying # against a single transaction. 'hashes': TransactionHash(self.trytes1), }, { 'hashes': [f.Type.CODE_WRONG_TYPE], }, )
def test_pass_happy_path(self): """ Request is valid. """ request = { 'depth': 100, 'minWeightMagnitude': 18, 'transaction': TransactionHash(self.trytes1), } filter_ = self._filter(request) self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, request)
def test_fail_hashes_wrong_type(self): """ ``hashes`` is not an array. """ self.assertFilterErrors( { # It's gotta be an array, even if there's only one hash. 'hashes': TransactionHash(self.hash1), }, { 'hashes': [f.Type.CODE_WRONG_TYPE], }, )
def test_fail_transactions_wrong_type(self): """ ``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), }, { 'transactions': [f.Type.CODE_WRONG_TYPE], }, )