class BroadcastAndStoreCommandTestCase(TestCase): # noinspection SpellCheckingInspection def setUp(self): super(BroadcastAndStoreCommandTestCase, self).setUp() self.adapter = MockAdapter() self.command = BroadcastAndStoreCommand(self.adapter) # Define a few valid values that we can reuse across tests. self.trytes1 = b'RBTC9D9DCDQAEASBYBCCKBFA' self.trytes2 =\ b'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHDWCTCEAKDCDFD9DSCSA' def test_wireup(self): """ Verify that the command is wired up correctly. """ self.assertIsInstance( Iota(self.adapter).broadcastAndStore, BroadcastAndStoreCommand, ) def test_happy_path(self): """ Successful invocation of ``broadcastAndStore``. """ self.adapter.seed_response( 'broadcastTransactions', { 'trytes': [ text_type(self.trytes1, 'ascii'), text_type(self.trytes2, 'ascii'), ], }) self.adapter.seed_response('storeTransactions', {}) trytes = [ TransactionTrytes(self.trytes1), TransactionTrytes(self.trytes2), ] response = self.command(trytes=trytes) self.assertDictEqual(response, {'trytes': trytes})
def test_routing(self): """ Routing commands to different adapters. """ default_adapter = MockAdapter() pow_adapter = MockAdapter() wrapper = ( RoutingWrapper(default_adapter) .add_route('attachToTangle', pow_adapter) .add_route('interruptAttachingToTangle', pow_adapter) ) default_adapter.seed_response('getNodeInfo', {'id': 'default1'}) pow_adapter.seed_response('attachToTangle', {'id': 'pow1'}) pow_adapter.seed_response('interruptAttachingToTangle', {'id': 'pow2'}) self.assertDictEqual( wrapper.send_request({'command': 'attachToTangle'}), {'id': 'pow1'}, ) self.assertDictEqual( wrapper.send_request({'command': 'interruptAttachingToTangle'}), {'id': 'pow2'}, ) # Any commands that aren't routed go to the default adapter. self.assertDictEqual( wrapper.send_request({'command': 'getNodeInfo'}), {'id': 'default1'}, )
class GetTipsCommandTestCase(TestCase): def setUp(self): super(GetTipsCommandTestCase, self).setUp() self.adapter = MockAdapter() def test_wireup(self): """ Verify that the command is wired up correctly. """ self.assertIsInstance( Iota(self.adapter).getTips, GetTipsCommand, ) def test_type_coercion(self): """ The result is coerced to the proper type. https://github.com/iotaledger/iota.lib.py/issues/130 """ # noinspection SpellCheckingInspection self.adapter.seed_response( 'getTips', { 'duration': 42, 'hashes': [ 'TESTVALUE9DONTUSEINPRODUCTION99999ANSVWB' 'CZ9ABZYUK9YYXFRLROGMCMQHRARDQPNMHHZSZ9999', 'TESTVALUE9DONTUSEINPRODUCTION99999HCZURL' 'NFWEDRFCYHWTYGUEMJLJ9ZIJTFASAVSEAZJGA9999', ], }) gt_response = Iota(self.adapter).get_tips() self.assertEqual( list(map(type, gt_response['hashes'])), [TransactionHash] * 2, )
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. """ self.assertIsInstance( Iota(self.adapter).getLatestInclusion, GetLatestInclusionCommand, ) def test_happy_path(self): """ Successfully requesting latest inclusion state. """ 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, }, } )
class GetInputsCommandTestCase(TestCase): # noinspection SpellCheckingInspection def setUp(self): super(GetInputsCommandTestCase, self).setUp() self.adapter = MockAdapter() self.command = GetInputsCommand(self.adapter) # Define some valid tryte sequences that we can reuse between # tests. self.addy0 =\ Address( trytes = b'TESTVALUE9DONTUSEINPRODUCTION99999FIODSG' b'IC9CCIFCNBTBDFIEHHE9RBAEVGK9JECCLCPBIINAX', key_index = 0, ) self.addy1 =\ Address( trytes = b'TESTVALUE9DONTUSEINPRODUCTION999999EPCNH' b'MBTEH9KDVFMHHESDOBTFFACCGBFGACEDCDDCGICIL', key_index = 1, ) self.addy2 =\ Address( trytes = b'TESTVALUE9DONTUSEINPRODUCTION99999YDOHWF' b'U9PFOFHGKFACCCBGDALGI9ZBEBABFAMBPDSEQ9XHJ', key_index = 2, ) def test_wireup(self): """ Verify that the command is wired up correctly. """ self.assertIsInstance( Iota(self.adapter).getInputs, GetInputsCommand, ) def test_stop_threshold_met(self): """ ``stop`` provided, balance meets ``threshold``. """ self.adapter.seed_response('getBalances', { 'balances': [42, 29], }) # 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. mock_address_generator = mock.Mock( return_value=[self.addy0, self.addy1]) with mock.patch( 'iota_async.crypto.addresses.AddressGenerator.get_addresses', mock_address_generator, ): response = self.command( seed=Seed.random(), stop=2, threshold=71, ) self.assertEqual(response['totalBalance'], 71) self.assertEqual(len(response['inputs']), 2) input0 = response['inputs'][0] self.assertIsInstance(input0, Address) self.assertEqual(input0, self.addy0) self.assertEqual(input0.balance, 42) self.assertEqual(input0.key_index, 0) input1 = response['inputs'][1] self.assertIsInstance(input1, Address) self.assertEqual(input1, self.addy1) self.assertEqual(input1.balance, 29) self.assertEqual(input1.key_index, 1) def test_stop_threshold_not_met(self): """ ``stop`` provided, balance does not meet ``threshold``. """ self.adapter.seed_response('getBalances', { 'balances': [42, 29], }) # 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. mock_address_generator = mock.Mock( return_value=[self.addy0, self.addy1]) with mock.patch( 'iota_async.crypto.addresses.AddressGenerator.get_addresses', mock_address_generator, ): with self.assertRaises(BadApiResponse): self.command( seed=Seed.random(), stop=2, threshold=72, ) def test_stop_threshold_zero(self): """ ``stop`` provided, ``threshold`` is 0. """ # Note that the first address has a zero balance. self.adapter.seed_response('getBalances', { 'balances': [0, 1], }) # 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. mock_address_generator = mock.Mock( return_value=[self.addy0, self.addy1]) with mock.patch( 'iota_async.crypto.addresses.AddressGenerator.get_addresses', mock_address_generator, ): response = self.command( seed=Seed.random(), stop=2, threshold=0, ) self.assertEqual(response['totalBalance'], 1) self.assertEqual(len(response['inputs']), 1) # Address 0 was skipped because it has a zero balance. input0 = response['inputs'][0] self.assertIsInstance(input0, Address) self.assertEqual(input0, self.addy1) self.assertEqual(input0.balance, 1) self.assertEqual(input0.key_index, 1) def test_stop_no_threshold(self): """ ``stop`` provided, no ``threshold``. """ self.adapter.seed_response('getBalances', { 'balances': [42, 29], }) # 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. mock_address_generator = mock.Mock( return_value=[self.addy0, self.addy1]) with mock.patch( 'iota_async.crypto.addresses.AddressGenerator.get_addresses', mock_address_generator, ): response = self.command( seed=Seed.random(), start=0, stop=2, ) self.assertEqual(response['totalBalance'], 71) self.assertEqual(len(response['inputs']), 2) input0 = response['inputs'][0] self.assertIsInstance(input0, Address) self.assertEqual(input0, self.addy0) self.assertEqual(input0.balance, 42) self.assertEqual(input0.key_index, 0) input1 = response['inputs'][1] self.assertIsInstance(input1, Address) self.assertEqual(input1, self.addy1) self.assertEqual(input1.balance, 29) self.assertEqual(input1.key_index, 1) def test_no_stop_threshold_met(self): """ No ``stop`` provided, balance meets ``threshold``. """ self.adapter.seed_response('getBalances', { 'balances': [42, 29], }) # ``getInputs`` uses ``findTransactions`` to identify unused # addresses. # noinspection SpellCheckingInspection self.adapter.seed_response( 'findTransactions', { 'hashes': [ TransactionHash( b'TESTVALUE9DONTUSEINPRODUCTION99999WBL9KD' b'EIZDMEDFPEYDIIA9LEMEUCC9MFPBY9TEVCUGSEGGN'), ], }) # 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): for addy in [self.addy0, 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(), threshold=71, ) self.assertEqual(response['totalBalance'], 71) self.assertEqual(len(response['inputs']), 2) input0 = response['inputs'][0] self.assertIsInstance(input0, Address) self.assertEqual(input0, self.addy0) self.assertEqual(input0.balance, 42) self.assertEqual(input0.key_index, 0) input1 = response['inputs'][1] self.assertIsInstance(input1, Address) self.assertEqual(input1, self.addy1) self.assertEqual(input1.balance, 29) self.assertEqual(input1.key_index, 1) def test_no_stop_threshold_not_met(self): """ No ``stop`` provided, balance does not meet ``threshold``. """ self.adapter.seed_response('getBalances', { 'balances': [42, 29, 0], }) # 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): for addy in [self.addy0, 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, ): with self.assertRaises(BadApiResponse): self.command( seed=Seed.random(), threshold=72, ) def test_no_stop_threshold_zero(self): """ No ``stop`` provided, ``threshold`` is 0. """ # Note that the first address has a zero balance. self.adapter.seed_response('getBalances', { 'balances': [0, 1], }) # ``getInputs`` uses ``findTransactions`` to identify unused # addresses. # noinspection SpellCheckingInspection self.adapter.seed_response( 'findTransactions', { 'hashes': [ TransactionHash( b'TESTVALUE9DONTUSEINPRODUCTION99999WBL9KD' b'EIZDMEDFPEYDIIA9LEMEUCC9MFPBY9TEVCUGSEGGN'), ], }) # 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): for addy in [self.addy0, 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(), threshold=0, ) self.assertEqual(response['totalBalance'], 1) self.assertEqual(len(response['inputs']), 1) # Because the first address had a zero balance, it was skipped. input0 = response['inputs'][0] self.assertIsInstance(input0, Address) self.assertEqual(input0, self.addy1) self.assertEqual(input0.balance, 1) self.assertEqual(input0.key_index, 1) def test_no_stop_no_threshold(self): """ No ``stop`` provided, no ``threshold``. """ self.adapter.seed_response('getBalances', { 'balances': [42, 29], }) # ``getInputs`` uses ``findTransactions`` to identify unused # addresses. # noinspection SpellCheckingInspection self.adapter.seed_response( 'findTransactions', { 'hashes': [ TransactionHash( b'TESTVALUE9DONTUSEINPRODUCTION99999WBL9KD' b'EIZDMEDFPEYDIIA9LEMEUCC9MFPBY9TEVCUGSEGGN'), ], }) # 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): for addy in [self.addy0, 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(), ) self.assertEqual(response['totalBalance'], 71) self.assertEqual(len(response['inputs']), 2) input0 = response['inputs'][0] self.assertIsInstance(input0, Address) self.assertEqual(input0, self.addy0) self.assertEqual(input0.balance, 42) self.assertEqual(input0.key_index, 0) input1 = response['inputs'][1] self.assertIsInstance(input1, Address) self.assertEqual(input1, self.addy1) self.assertEqual(input1.balance, 29) self.assertEqual(input1.key_index, 1) 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 test_start_stop(self): """ Using ``start`` and ``stop`` at once. Checking if correct number of addresses is returned. Must be stop - start """ # 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): # returning up to 3 addresses, depending on stop value for addy in [self.addy0, self.addy1, self.addy2][start::step]: yield addy self.adapter.seed_response('getBalances', { 'balances': [11, 11], }) with mock.patch( 'iota_async.crypto.addresses.AddressGenerator.create_iterator', mock_address_generator, ): response = self.command( seed=Seed.random(), start=1, stop=3, ) self.assertEqual(len(response['inputs']), 2) # 3 - 1 = 2 addresses expected self.assertEqual(response['totalBalance'], 22) input0 = response['inputs'][0] self.assertIsInstance(input0, Address) self.assertEqual(input0, self.addy1) self.assertEqual(input0.balance, 11) self.assertEqual(input0.key_index, 1) input1 = response['inputs'][1] self.assertIsInstance(input1, Address) self.assertEqual(input1, self.addy2) self.assertEqual(input1.balance, 11) self.assertEqual(input1.key_index, 2) def test_security_level_1_no_stop(self): """ Testing GetInputsCoommand: - with security_level = 1 (non default) - without `stop` parameter """ # one address with index 0 for selected security levels for the random seed. # to check with respective outputs from command seed = Seed.random() address = AddressGenerator(seed, security_level=1).get_addresses(0)[0] 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': [], }) response = GetInputsCommand(self.adapter)( seed=seed, securityLevel=1, ) self.assertEqual(response['totalBalance'], 86) self.assertEqual(len(response['inputs']), 1) input0 = response['inputs'][0] self.assertIsInstance(input0, Address) self.assertEqual(input0, address) self.assertEqual(input0.balance, 86) self.assertEqual(input0.key_index, 0) def test_security_level_1_with_stop(self): """ Testing GetInputsCoommand: - with security_level = 1 (non default) - with `stop` parameter """ # one address with index 0 for selected security levels for the random seed. # to check with respective outputs from command seed = Seed.random() address = AddressGenerator(seed, security_level=1).get_addresses(0)[0] 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': [], }) response = GetInputsCommand(self.adapter)( seed=seed, securityLevel=1, stop=1, # <<<<< here ) self.assertEqual(response['totalBalance'], 86) self.assertEqual(len(response['inputs']), 1) input0 = response['inputs'][0] self.assertIsInstance(input0, Address) self.assertEqual(input0, address) self.assertEqual(input0.balance, 86) self.assertEqual(input0.key_index, 0)
class CustomCommandTestCase(TestCase): def setUp(self): super(CustomCommandTestCase, self).setUp() self.name = 'helloWorld' self.adapter = MockAdapter() self.command = CustomCommand(self.adapter, self.name) def test_call(self): """ Sending a custom command. """ expected_response = {'message': 'Hello, IOTA!'} self.adapter.seed_response('helloWorld', expected_response) response = self.command() self.assertEqual(response, expected_response) self.assertTrue(self.command.called) self.assertListEqual( self.adapter.requests, [{'command': 'helloWorld'}], ) def test_call_with_parameters(self): """ Sending a custom command with parameters. """ expected_response = {'message': 'Hello, IOTA!'} self.adapter.seed_response('helloWorld', expected_response) response = self.command(foo='bar', baz='luhrmann') self.assertEqual(response, expected_response) self.assertTrue(self.command.called) self.assertListEqual( self.adapter.requests, [{'command': 'helloWorld', 'foo': 'bar', 'baz': 'luhrmann'}], ) def test_call_error_already_called(self): """ A command can only be called once. """ self.adapter.seed_response('helloWorld', {}) self.command() with self.assertRaises(RuntimeError): self.command(extra='params') self.assertDictEqual(self.command.request, {'command': 'helloWorld'}) def test_call_reset(self): """ Resetting a command allows it to be called more than once. """ self.adapter.seed_response('helloWorld', {'message': 'Hello, IOTA!'}) self.command() self.command.reset() self.assertFalse(self.command.called) self.assertIsNone(self.command.request) self.assertIsNone(self.command.response) expected_response = {'message': 'Welcome back!'} self.adapter.seed_response('helloWorld', expected_response) response = self.command(foo='bar') self.assertDictEqual(response, expected_response) self.assertDictEqual(self.command.response, expected_response) self.assertDictEqual( self.command.request, { 'command': 'helloWorld', 'foo': 'bar', }, )
class PrepareMultisigTransferCommandTestCase(TestCase): # noinspection SpellCheckingInspection def setUp(self): super(PrepareMultisigTransferCommandTestCase, self).setUp() self.adapter = MockAdapter() self.command = PrepareMultisigTransferCommand(self.adapter) # Define some tryte sequences that we can reuse between tests. self.digest_1 =\ Digest( trytes = b'FWNEPVJNGUKTSHSBDO9AORBCVWWLVXC9KAMKYYNKPYNJDKSAUURI9ELKOEEYPKVTYP' b'CKOCJQESYFEMINIFKX9PDDGRBEEHYYXCJW9LHGWFZGHKCPVDBGMGQKIPCNKNITGMZT' b'DIWVUB9PCHCOPHMIWKSUKRHZOJPMAY', key_index = 0, ) self.digest_2 =\ Digest( trytes = b'PAIRLDJQY9XAUSKIGCTHRJHZVARBEY9NNHYJ9UI9HWWZXFSDWEZEGDCWNVVYSYDV9O' b'HTR9NGGZURISWTNECFTCMEWQQFJ9VKLFPDTYJYXC99OLGRH9OSFJLMEOGHFDHZYEAF' b'IMIZTJRBQUVCR9U9ZWTMUXTUEOUBLC', key_index = 0, ) self.trytes_1 = ( b'TESTVALUE9DONTUSEINPRODUCTION99999IIPEM9' b'LA9FLHEGHDACSA9DOBQHQCX9BBHCFDIIMACARHA9B' ) self.trytes_2 = ( b'TESTVALUE9DONTUSEINPRODUCTION99999BGUDVE' b'DGH9WFQDEDVETCOGEGCDI9RFHGFGXBI99EJICHNEM' ) self.trytes_3 = ( b'TESTVALUE9DONTUSEINPRODUCTION99999XBGEUC' b'LF9EIFXHM9KHQANBLBHFVGTEGBWHNAKFDGZHYGCHI' ) def test_wireup(self): """ Verifies the command is wired up correctly. """ self.assertIsInstance( MultisigIota(self.adapter).prepareMultisigTransfer, PrepareMultisigTransferCommand, ) def test_happy_path(self): """ Preparing a bundle with a multisig input. """ self.adapter.seed_response( command = GetBalancesCommand.command, response = { 'balances': [42], # Would it be cheeky to put "7½ million years" here? 'duration': 86, }, ) pmt_result =\ self.command( transfers = [ ProposedTransaction( address = Address(self.trytes_1), value = 42, ), ], multisigInput = MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), ) # The command returns the raw trytes. This is useful in a # real-world scenario because trytes are easier to transfer between # each entity that needs to apply their signature. # # However, for purposes of this test, we will convert the trytes # back into a bundle so that we can inspect the end result more # easily. bundle = Bundle.from_tryte_strings(pmt_result['trytes']) # # This bundle looks almost identical to what you would expect from # :py:meth:`iota_async.api.Iota.prepare_transfer`, except: # - There are 4 inputs (to hold all of the signature fragments). # - The inputs are unsigned. # self.assertEqual(len(bundle), 5) # Spend Transaction txn_1 = bundle[0] self.assertEqual(txn_1.address, self.trytes_1) self.assertEqual(txn_1.value, 42) # Input 1, Part 1 of 4 txn_2 = bundle[1] self.assertEqual(txn_2.address, self.trytes_2) self.assertEqual(txn_2.value, -42) self.assertEqual(txn_2.signature_message_fragment, Fragment(b'')) # Input 1, Part 2 of 4 txn_3 = bundle[2] self.assertEqual(txn_3.address, self.trytes_2) self.assertEqual(txn_3.value, 0) self.assertEqual(txn_3.signature_message_fragment, Fragment(b'')) # Input 1, Part 3 of 4 txn_4 = bundle[3] self.assertEqual(txn_4.address, self.trytes_2) self.assertEqual(txn_4.value, 0) self.assertEqual(txn_4.signature_message_fragment, Fragment(b'')) # Input 1, Part 4 of 4 txn_5 = bundle[4] self.assertEqual(txn_5.address, self.trytes_2) self.assertEqual(txn_5.value, 0) self.assertEqual(txn_5.signature_message_fragment, Fragment(b'')) def test_unspent_inputs_with_change_address(self): """ The bundle has unspent inputs, so it uses the provided change address. """ self.adapter.seed_response( command = GetBalancesCommand.command, response = { 'balances': [101], 'duration': 86, }, ) pmt_result =\ self.command( transfers = [ ProposedTransaction( address = Address(self.trytes_1), value = 42, ), ], multisigInput = MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), changeAddress = Address(self.trytes_3), ) bundle = Bundle.from_tryte_strings(pmt_result['trytes']) self.assertEqual(len(bundle), 6) # Spend Transaction txn_1 = bundle[0] self.assertEqual(txn_1.address, self.trytes_1) self.assertEqual(txn_1.value, 42) # Input 1, Part 1 of 4 txn_2 = bundle[1] self.assertEqual(txn_2.address, self.trytes_2) self.assertEqual(txn_2.value, -101) self.assertEqual(txn_2.signature_message_fragment, Fragment(b'')) # Input 1, Part 2 of 4 txn_3 = bundle[2] self.assertEqual(txn_3.address, self.trytes_2) self.assertEqual(txn_3.value, 0) self.assertEqual(txn_3.signature_message_fragment, Fragment(b'')) # Input 1, Part 3 of 4 txn_4 = bundle[3] self.assertEqual(txn_4.address, self.trytes_2) self.assertEqual(txn_4.value, 0) self.assertEqual(txn_4.signature_message_fragment, Fragment(b'')) # Input 1, Part 4 of 4 txn_5 = bundle[4] self.assertEqual(txn_5.address, self.trytes_2) self.assertEqual(txn_5.value, 0) self.assertEqual(txn_5.signature_message_fragment, Fragment(b'')) # Change txn_6 = bundle[5] self.assertEqual(txn_6.address, self.trytes_3) self.assertEqual(txn_6.value, 59) def test_error_zero_iotas_transferred(self): """ The bundle doesn't spend any IOTAs. This is considered an error case because :py:meth:`MultisigIota.prepare_multisig_transfer` is specialized for generating bundles that require multisig inputs. Any bundle that doesn't require multisig functionality should be generated using :py:meth:`iota_async.api.Iota.prepare_transfer` instead. """ with self.assertRaises(ValueError): self.command( transfers = [ ProposedTransaction( address = Address(self.trytes_1), value = 0, ), ], multisigInput = MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), ) def test_error_insufficient_inputs(self): """ The multisig input does not contain sufficient IOTAs to cover the spends. """ self.adapter.seed_response( command = GetBalancesCommand.command, response = { 'balances': [42], 'duration': 86, }, ) with self.assertRaises(ValueError): self.command( transfers = [ ProposedTransaction( address = Address(self.trytes_1), value = 101, ), ], multisigInput = MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), ) def test_error_unspent_inputs_no_change_address(self): """ The bundle has unspent inputs, but no change address was specified. Unlike :py:meth:`iota_async.api.Iota.prepare_transfer` where all of the inputs are owned by the same seed, creating a multisig transfer usually involves multiple people. It would be unfair to the participants of the transaction if we were to automatically generate a change address using the seed of whoever happened to invoke the :py:meth:`MultisigIota.prepare_multisig_transfer` method! """ self.adapter.seed_response( command = GetBalancesCommand.command, response = { 'balances': [101], 'duration': 86, }, ) with self.assertRaises(ValueError): self.command( transfers = [ ProposedTransaction( address = Address(self.trytes_1), value = 42, ), ], multisigInput = MultisigAddress( digests = [self.digest_1, self.digest_2], trytes = self.trytes_2, ), # changeAddress = Address(self.trytes_3), )
class SendTrytesCommandTestCase(TestCase): # noinspection SpellCheckingInspection def setUp(self): super(SendTrytesCommandTestCase, self).setUp() self.adapter = MockAdapter() self.command = SendTrytesCommand(self.adapter) # Define a few valid values that we can reuse across tests. self.trytes1 = b'RBTC9D9DCDQAEASBYBCCKBFA' self.trytes2 =\ b'CCPCBDVC9DTCEAKDXC9D9DEARCWCPCBDVCTCEAHDWCTCEAKDCDFD9DSCSA' self.transaction1 = (b'TKGDZ9GEI9CPNQGHEATIISAKYPPPSXVCXBSR9EIW' b'CTHHSSEQCD9YLDPEXYERCNJVASRGWMAVKFQTC9999') self.transaction2 = (b'TKGDZ9GEI9CPNQGHEATIISAKYPPPSXVCXBSR9EIW' b'CTHHSSEQCD9YLDPEXYERCNJVASRGWMAVKFQTC9999') def test_wireup(self): """ Verify that the command is wired up correctly. """ self.assertIsInstance( Iota(self.adapter).sendTrytes, SendTrytesCommand, ) def test_happy_path(self): """ Successful invocation of ``sendTrytes``. """ self.adapter.seed_response( 'getTransactionsToApprove', { 'trunkTransaction': text_type(self.transaction1, 'ascii'), 'branchTransaction': text_type(self.transaction2, 'ascii'), }) self.adapter.seed_response( 'attachToTangle', { 'trytes': [ text_type(self.trytes1, 'ascii'), text_type(self.trytes2, 'ascii'), ], }) self.adapter.seed_response('broadcastTransactions', {}) self.adapter.seed_response('storeTransactions', {}) trytes = [ TransactionTrytes(self.trytes1), TransactionTrytes(self.trytes2), ] response = self.command( trytes=trytes, depth=100, minWeightMagnitude=18, ) self.assertDictEqual(response, {'trytes': trytes})
class GetNewAddressesCommandTestCase(TestCase): # noinspection SpellCheckingInspection def setUp(self): super(GetNewAddressesCommandTestCase, self).setUp() self.adapter = MockAdapter() self.command = GetNewAddressesCommand(self.adapter) self.seed =\ Seed( b'TESTVALUE9DONTUSEINPRODUCTION99999ZDCCUF' b'CBBIQCLGMEXAVFQEOF9DRAB9VCEBAGXAF9VF9FLHP', ) self.addy_1 =\ Address( b'NYMWLBUJEISSACZZBRENC9HEHYQXHCGQHSNHVCEA' b'ZDCTEVNGSDUEKTSYBSQGMVJRIEDHWDYSEYCFAZAH9', ) self.addy_2 =\ Address( b'NTPSEVZHQITARYWHIRTSIFSERINLRYVXLGIQKKHY' b'IWYTLQUUHDWSOVXLIKVJTYZBFKLABWRBFYVSMD9NB', ) self.addy_1_checksum =\ Address( b'NYMWLBUJEISSACZZBRENC9HEHYQXHCGQHSNHVCEA' b'ZDCTEVNGSDUEKTSYBSQGMVJRIEDHWDYSEYCFAZAH' b'9T9FPJROTW', ) def test_wireup(self): """ Verify that the command is wired up correctly. """ self.assertIsInstance( Iota(self.adapter).getNewAddresses, GetNewAddressesCommand, ) def test_get_addresses_offline(self): """ Generate addresses in offline mode (without filtering used addresses). """ response =\ self.command( count = 2, index = 0, seed = self.seed, ) self.assertDictEqual( response, {'addresses': [self.addy_1, self.addy_2]}, ) # No API requests were made. self.assertListEqual(self.adapter.requests, []) def test_security_level(self): """ Generating addresses with a different security level. """ response =\ self.command( count = 2, index = 0, securityLevel = 1, seed = self.seed, ) # noinspection SpellCheckingInspection self.assertDictEqual( response, { 'addresses': [ Address( b'ERBTZTPT9SKDQEGETKMZLYNRQMZYZIDENGWCSGRF' b'9TLURIEFVKUBSWOIMLMWTWMWTTHSUREPISXDPLCQC', ), Address( b'QVHEMGYHVMCFAISJKTWPFSKDAFRZHXQZK9E9KOUQ' b'LOLVBN9BFAZDDY9O9EYYMHMDWZAKXI9OPBPEYM9FC', ), ], }, ) def test_get_addresses_online(self): """ Generate address in online mode (filtering used addresses). """ # Pretend that ``self.addy1`` has already been used, but not # ``self.addy2``. # noinspection SpellCheckingInspection self.adapter.seed_response('findTransactions', { 'duration': 18, 'hashes': [ 'TESTVALUE9DONTUSEINPRODUCTION99999ITQLQN' 'LPPG9YNAARMKNKYQO9GSCSBIOTGMLJUFLZWSY9999', ], }) self.adapter.seed_response('findTransactions', { 'duration': 1, 'hashes': [], }) response =\ self.command( # If ``count`` is missing or ``None``, the command will operate # in online mode. # count = None, index = 0, seed = self.seed, ) # The command determined that ``self.addy1`` was already used, so # it skipped that one. self.assertDictEqual(response, {'addresses': [self.addy_2]}) self.assertListEqual( self.adapter.requests, # The command issued two `findTransactions` API requests: one for # each address generated, until it found an unused address. [ { 'command': 'findTransactions', 'addresses': [self.addy_1], }, { 'command': 'findTransactions', 'addresses': [self.addy_2], }, ], ) def test_new_address_checksum(self): """ Generate address with a checksum. """ response =\ self.command( checksum = True, count = 1, index = 0, seed = self.seed, ) self.assertDictEqual( response, {'addresses': [self.addy_1_checksum]}, )
class PromoteTransactionCommandTestCase(TestCase): 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_wireup(self): """ Verifies that the command is wired-up correctly. """ self.assertIsInstance( Iota(self.adapter).promoteTransaction, PromoteTransactionCommand, ) def test_happy_path(self): """ Successfully promoting a bundle. """ self.adapter.seed_response('checkConsistency', { 'state': True, }) result_bundle = Bundle.from_tryte_strings([ TransactionTrytes(self.trytes1), TransactionTrytes(self.trytes2), ]) mock_send_transfer = mock.Mock(return_value={ 'bundle': result_bundle, }) with mock.patch( 'iota_async.commands.extended.send_transfer.SendTransferCommand._execute', mock_send_transfer, ): response = self.command( transaction=self.hash1, depth=3, minWeightMagnitude=16, ) self.assertDictEqual(response, { 'bundle': result_bundle, }) def test_not_promotable(self): """ Bundle isn't promotable. """ self.adapter.seed_response('checkConsistency', { 'state': False, }) with self.assertRaises(BadApiResponse): response = self.command( transaction=self.hash1, depth=3, minWeightMagnitude=16, )
class CheckConsistencyCommandTestCase(TestCase): # noinspection SpellCheckingInspection def setUp(self): super(CheckConsistencyCommandTestCase, self).setUp() self.adapter = MockAdapter() self.command = CheckConsistencyCommand(self.adapter) # Define some tryte sequences that we can re-use across tests. self.milestone =\ TransactionHash( b'TESTVALUE9DONTUSEINPRODUCTION99999W9KDIH' b'BALAYAFCADIDU9HCXDKIXEYDNFRAKHN9IEIDZFWGJ' ) self.hash1 =\ TransactionHash( b'TESTVALUE9DONTUSEINPRODUCTION99999TBPDM9' b'ADFAWCKCSFUALFGETFIFG9UHIEFE9AYESEHDUBDDF' ) self.hash2 =\ TransactionHash( b'TESTVALUE9DONTUSEINPRODUCTION99999CIGCCF' b'KIUFZF9EP9YEYGQAIEXDTEAAUGAEWBBASHYCWBHDX' ) def test_wireup(self): """ Verify that the command is wired up correctly. """ self.assertIsInstance( Iota(self.adapter).checkConsistency, CheckConsistencyCommand, ) def test_happy_path(self): """ Successfully checking consistency. """ self.adapter.seed_response('checkConsistency', { 'state': True, }) response = self.command(tails=[self.hash1, self.hash2]) self.assertDictEqual( response, { 'state': True, } ) def test_info_with_false_state(self): """ `info` field exists when `state` is False. """ self.adapter.seed_response('checkConsistency', { 'state': False, 'info': 'Additional information', }) response = self.command(tails=[self.hash1, self.hash2]) self.assertDictEqual( response, { 'state': False, 'info': 'Additional information', } )
class GetBundlesCommandTestCase(TestCase): def setUp(self): super(GetBundlesCommandTestCase, self).setUp() self.adapter = MockAdapter() self.command = GetBundlesCommand(self.adapter) def test_wireup(self): """ Verifies that the command is wired up correctly. """ self.assertIsInstance( Iota(self.adapter).getBundles, GetBundlesCommand, ) def test_single_transaction(self): """ Getting a bundle that contains a single transaction. """ transaction =\ Transaction( current_index = 0, last_index = 0, tag = Tag(b''), timestamp = 1484960990, value = 0, attachment_timestamp = 1484960990, attachment_timestamp_lower_bound = 12, attachment_timestamp_upper_bound = 0, # These values are not relevant for 0-value transactions. nonce = Nonce(b''), signature_message_fragment = Fragment(b''), # This value is computed automatically, so it has to be real. hash_ = TransactionHash( b'XPJIYZWPF9LBCYZPNBFARDRCSUGJGF9TWZT9K9PX' b'VYDFPZOZBGXUCKLTJEUCFBEKQQ9VCSQVQDMMJQAY9', ), address = Address( b'TESTVALUE9DONTUSEINPRODUCTION99999OCSGVF' b'IBQA99KGTCPCZ9NHR9VGLGADDDIEGGPCGBDEDDTBC', ), bundle_hash = BundleHash( b'TESTVALUE9DONTUSEINPRODUCTION99999DIOAZD' b'M9AIUHXGVGBC9EMGI9SBVBAIXCBFJ9EELCPDRAD9U', ), branch_transaction_hash = TransactionHash( b'TESTVALUE9DONTUSEINPRODUCTION99999BBCEDI' b'ZHUDWBYDJEXHHAKDOCKEKDFIMB9AMCLFW9NBDEOFV', ), trunk_transaction_hash = TransactionHash( b'TESTVALUE9DONTUSEINPRODUCTION999999ARAYA' b'MHCB9DCFEIWEWDLBCDN9LCCBQBKGDDAECFIAAGDAS', ), ) self.adapter.seed_response('getTrytes', { 'trytes': [transaction.as_tryte_string()], }) response = self.command(transaction=transaction.hash) bundle = response['bundles'][0] # type: Bundle self.assertEqual(len(bundle), 1) self.maxDiff = None self.assertDictEqual( bundle[0].as_json_compatible(), transaction.as_json_compatible(), ) def test_multiple_transactions(self): """ Getting a bundle that contains multiple transactions. """ bundle = Bundle.from_tryte_strings([ TransactionTrytes( 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'9PWQHOBXNNQIF9IRHVQXKPZW999999999999999999999999999XZUIENOTTBKJMDP' b'RXWGQYG9PWGTHNLFMVD99A99999999A99999999PDQWLVVDPUU9VIBODGMRIAZPGQX' b'DOGSEXIHKIBWSLDAWUKZCZMK9Z9YZSPCKBDJSVDPRQLJSTKUMTNVSXBGUEHHGAIWWQ' b'BCJZHZAQOWZMAIDAFUZBVMUVPWQJLUGGQKNKLMGTWXXNZKUCBJLEDAMYVRGABAWBY9' b'999MYIYBTGIOQYYZFJBLIAWMPSZEFFTXUZPCDIXSLLQDQSFYGQSQOGSPKCZNLVSZ9L' b'MCUWVNGEN9EJEW9999XZUIENOTTBKJMDPRXWGQYG9PWGTXUO9AXMP9FLMDRMADLRPW' b'CZCJBROYCDRJMYU9HDYJM9NDBFUPIZVTR' ), # Well, it was bound to happen sooner or later... the ASCII # representation of this tryte sequence contains a very naughty # phrase. But I don't feel like doing another POW, so... enjoy. TransactionTrytes( b'NBTCPCFDEACCPCBDVC9DTCQAJ9RBTC9D9DCDQAEAKDCDFD9DSCFAJ9VBCDJDTCQAJ9' b'ZBMDYBCCKB99999999999999999999999999999999999999999999999999999999' 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'999999999999999999999999999999999999999999999999999SYRABNN9JD9PNDL' b'IKUNCECUELTOHNLFMVD99999999999A99999999PDQWLVVDPUU9VIBODGMRIAZPGQX' b'DOGSEXIHKIBWSLDAWUKZCZMK9Z9YZSPCKBDJSVDPRQLJSTKUMTNVSXFSEWUNJOEGNU' b'I9QOCRFMYSIFAZLJHKZBPQZZYFG9ORYCRDX9TOMJPFCRB9R9KPUUGFPVOWYXFIWEW9' b'999BGUEHHGAIWWQBCJZHZAQOWZMAIDAFUZBVMUVPWQJLUGGQKNKLMGTWXXNZKUCBJL' b'EDAMYVRGABAWBY9999SYRABNN9JD9PNDLIKUNCECUELTOQZPSBDILVHJQVCEOICFAD' b'YKZVGMOAXJRQNTCKMHGTAUMPGJJMX9LNF' ), ]) for txn in bundle: self.adapter.seed_response('getTrytes', { 'trytes': [txn.as_tryte_string()], }) self.adapter.seed_response('getTrytes', { 'trytes': [ 'SPAMSPAMSPAM999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999999999999999999' '999999999999999999999999999999999999999999999999999JECDITWO9999999' '999999999999ONLFMVD99999999999999999999VVCHSQSRVFKSBONDWB9EAQEMQOY' 'YRBIZHTBJLYNAVDHZPUZAZ9LYHXWKBEJ9IPR9FAMFLT9EEOHVYWUPRHHSRCILCLWFD' 'GBYBFFOKMCSAPVD9VGZZRRGBLGMZMXD9RMZQDBLMGN9BATWZGULRBCYQEIKIRBPHC9' '999KTLTRSYOWBD9HVNP9GCUABARNGMYXUZKXWRPGOPETZLKYYC9Z9EYXIWVARUBMBM' 'BPXGORN9WPBLY99999ZRBVQWULRFXDNDYZKRKIXPZQT9JJJH9FZU9PVWZJWLXBPODP' 'EHMKTTAGEPLPHUQCZNLDSHERONOMHJCOI' ], }) response = self.command( transaction = TransactionHash( b'TOYJPHKMLQNDVLDHDILARUJCCIUMQBLUSWPCTIVA' b'DRXICGYDGSVPXFTILFFGAPICYHGGJ9OHXINFX9999' ), ) self.maxDiff = None self.assertListEqual( response['bundles'][0].as_json_compatible(), bundle.as_json_compatible(), ) 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_missing_transaction(self): """ Unable to find the requested transaction. """ self.adapter.seed_response('getTrytes', {'trytes': []}) with self.assertRaises(BadApiResponse): self.command( transaction = TransactionHash( b'FSEWUNJOEGNUI9QOCRFMYSIFAZLJHKZBPQZZYFG9' b'ORYCRDX9TOMJPFCRB9R9KPUUGFPVOWYXFIWEW9999' ), )
class GetTransfersCommandTestCase(TestCase): def setUp(self): super(GetTransfersCommandTestCase, self).setUp() 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. """ self.assertIsInstance( Iota(self.adapter).getTransfers, GetTransfersCommand, ) def test_full_scan(self): """ Scanning the Tangle for all transfers. """ # To speed up the test, we will mock the address generator. # :py:class:`iota_async.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, 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, # Thankfully, we do not have to seed a realistic response for # ``getTrytes``, as we will be mocking the ``getBundles`` # command that uses on it. '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, attachment_timestamp=1483033814, attachment_timestamp_lower_bound=12, attachment_timestamp_upper_bound=0, nonce=None, ) ]) mock_get_bundles =\ mock.Mock(return_value={ 'bundles': [bundle], }) with mock.patch( 'iota_async.crypto.addresses.AddressGenerator.create_iterator', create_generator, ): with mock.patch( 'iota_async.commands.extended.get_bundles.GetBundlesCommand._execute', mock_get_bundles, ): response = self.command(seed=Seed.random()) self.assertDictEqual( response, { 'bundles': [bundle], }, ) def test_no_transactions(self): """ There are no transactions for the specified seed. """ # To speed up the test, we will mock the address generator. # :py:class:`iota_async.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', { 'duration': 1, 'hashes': [], }, ) with mock.patch( 'iota_async.crypto.addresses.AddressGenerator.create_iterator', create_generator, ): response = self.command(seed=Seed.random()) self.assertDictEqual(response, {'bundles': []}) def test_start(self): """ Scanning the Tangle for all transfers, with start index. """ # 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, attachment_timestamp=1483033814, attachment_timestamp_lower_bound=12, attachment_timestamp_upper_bound=0, nonce=None, ) ]) mock_get_bundles = mock.Mock(return_value={ 'bundles': [bundle], }) with mock.patch( 'iota_async.crypto.addresses.AddressGenerator.create_iterator', create_generator, ): with mock.patch( 'iota_async.commands.extended.get_bundles.GetBundlesCommand._execute', mock_get_bundles, ): response = self.command(seed=Seed.random(), start=1) self.assertDictEqual( response, { 'bundles': [bundle], }, ) def test_stop(self): """ Scanning the Tangle for all transfers, with stop index. """ # 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, attachment_timestamp=1483033814, attachment_timestamp_lower_bound=12, attachment_timestamp_upper_bound=0, nonce=None, ) ]) mock_get_bundles = mock.Mock(return_value={ 'bundles': [bundle], }) with mock.patch( 'iota_async.crypto.addresses.AddressGenerator.create_iterator', create_generator, ): with mock.patch( 'iota_async.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. """ # 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.Mock(return_value={ 'bundles': [Bundle([transaction])], }) mock_get_latest_inclusion = mock.Mock(return_value={ 'states': { transaction.hash: True, }, }) with mock.patch( 'iota_async.crypto.addresses.AddressGenerator.create_iterator', create_generator, ): with mock.patch( 'iota_async.commands.extended.get_bundles.GetBundlesCommand._execute', mock_get_bundles, ): with mock.patch( 'iota_async.commands.extended.get_latest_inclusion.GetLatestInclusionCommand._execute', mock_get_latest_inclusion, ): response = self.command( seed=Seed.random(), inclusionStates=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)