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_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] # ``getInputs`` uses ``findTransactions`` and # ``wereAddressesSpentFrom`` to identify unused addresses. # noinspection SpellCheckingInspection self.adapter.seed_response( 'findTransactions', { 'hashes': [ TransactionHash( b'TESTVALUE9DONTUSEINPRODUCTION99999YFXGOD' b'GISBJAX9PDJIRDMDV9DCRDCAEG9FN9KECCBDDFZ9H'), ], }) self.adapter.seed_response('findTransactions', { 'hashes': [], }) self.adapter.seed_response('wereAddressesSpentFrom', { 'states': [False], }) self.adapter.seed_response('getBalances', { 'balances': [86], }) 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)
class GetInputsRequestFilterTestCase(BaseFilterTestCase): filter_type = GetInputsCommand(MockAdapter()).get_request_filter skip_value_check = True # noinspection SpellCheckingInspection def setUp(self): super(GetInputsRequestFilterTestCase, self).setUp() # Define a few tryte sequences that we can re-use between tests. self.seed = b'HELLOIOTA' def test_pass_happy_path(self): """ Request is valid. """ request = { 'seed': Seed(self.seed), 'start': 0, 'stop': 10, 'threshold': 100, } filter_ = self._filter(request) self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, request) def test_pass_compatible_types(self): """ The request contains values that can be converted to the expected types. """ filter_ = self._filter({ # ``seed`` can be any value that is convertible into a # TryteString. 'seed': binary_type(self.seed), # These values must still be integers, however. 'start': 42, 'stop': 86, 'threshold': 99, }) self.assertFilterPasses(filter_) self.assertDictEqual( filter_.cleaned_data, { 'seed': Seed(self.seed), 'start': 42, 'stop': 86, 'threshold': 99, }, ) def test_pass_optional_parameters_excluded(self): """ The request contains only required parameters. """ filter_ = self._filter({ 'seed': Seed(self.seed), }) self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, { 'seed': Seed(self.seed), 'start': 0, 'stop': None, 'threshold': None, }) def test_fail_empty_request(self): """ The request is empty. """ self.assertFilterErrors( {}, { 'seed': [f.FilterMapper.CODE_MISSING_KEY], }, ) def test_fail_unexpected_parameters(self): """ The request contains unexpected parameters. """ self.assertFilterErrors( { 'seed': Seed(self.seed), # Told you I did. Reckless is he. Now, matters are worse. 'foo': 'bar', }, { 'foo': [f.FilterMapper.CODE_EXTRA_KEY], }, ) def test_fail_seed_null(self): """ ``seed`` is null. """ self.assertFilterErrors( { 'seed': None, }, { 'seed': [f.Required.CODE_EMPTY], }, ) def test_fail_seed_wrong_type(self): """ ``seed`` cannot be converted into a TryteString. """ self.assertFilterErrors( { 'seed': text_type(self.seed, 'ascii'), }, { 'seed': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_seed_malformed(self): """ ``seed`` has the correct type, but it contains invalid characters. """ self.assertFilterErrors( { 'seed': b'not valid; seeds can only contain uppercase and "9".', }, { 'seed': [Trytes.CODE_NOT_TRYTES], }, ) def test_fail_start_string(self): """ ``start`` is a string. """ self.assertFilterErrors( { # Not valid; it must be an int. 'start': '0', 'seed': Seed(self.seed), }, { 'start': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_start_float(self): """ ``start`` is a float. """ self.assertFilterErrors( { # Even with an empty fpart, floats are not valid. # It's gotta be an int. 'start': 8.0, 'seed': Seed(self.seed), }, { 'start': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_start_too_small(self): """ ``start`` is less than 0. """ self.assertFilterErrors( { 'start': -1, 'seed': Seed(self.seed), }, { 'start': [f.Min.CODE_TOO_SMALL], }, ) def test_fail_stop_string(self): """ ``stop`` is a string. """ self.assertFilterErrors( { # Not valid; it must be an int. 'stop': '0', 'seed': Seed(self.seed), }, { 'stop': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_stop_float(self): """ ``stop`` is a float. """ self.assertFilterErrors( { # Even with an empty fpart, floats are not valid. # It's gotta be an int. 'stop': 8.0, 'seed': Seed(self.seed), }, { 'stop': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_stop_too_small(self): """ ``stop`` is less than 0. """ self.assertFilterErrors( { 'stop': -1, 'seed': Seed(self.seed), }, { 'stop': [f.Min.CODE_TOO_SMALL], }, ) def test_fail_stop_occurs_before_start(self): """ ``stop`` is less than ``start``. """ self.assertFilterErrors( { 'start': 1, 'stop': 0, 'seed': Seed(self.seed), }, { 'start': [GetInputsRequestFilter.CODE_INTERVAL_INVALID], }, ) def test_fail_interval_too_large(self): """ ``stop`` is way more than ``start``. """ self.assertFilterErrors( { 'start': 0, 'stop': GetInputsRequestFilter.MAX_INTERVAL + 1, 'seed': Seed(self.seed), }, { 'stop': [GetInputsRequestFilter.CODE_INTERVAL_TOO_BIG], }, ) def test_fail_threshold_string(self): """ ``threshold`` is a string. """ self.assertFilterErrors( { # Not valid; it must be an int. 'threshold': '0', 'seed': Seed(self.seed), }, { 'threshold': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_threshold_float(self): """ ``threshold`` is a float. """ self.assertFilterErrors( { # Even with an empty fpart, floats are not valid. # It's gotta be an int. 'threshold': 8.0, 'seed': Seed(self.seed), }, { 'threshold': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_threshold_too_small(self): """ ``threshold`` is less than 0. """ self.assertFilterErrors( { 'threshold': -1, 'seed': Seed(self.seed), }, { 'threshold': [f.Min.CODE_TOO_SMALL], }, )
def _execute(self, request): # Required parameters. seed = request['seed'] # type: Seed bundle = ProposedBundle(request['transfers']) # Optional parameters. change_address = request.get( 'changeAddress') # type: Optional[Address] proposed_inputs = request.get( 'inputs') # type: Optional[List[Address]] want_to_spend = bundle.balance if want_to_spend > 0: # We are spending inputs, so we need to gather and sign them. if proposed_inputs is None: # No inputs provided. Scan addresses for unspent inputs. gi_response = GetInputsCommand(self.adapter)( seed=seed, threshold=want_to_spend, ) confirmed_inputs = gi_response['inputs'] else: # Inputs provided. Check to make sure we have sufficient # balance. available_to_spend = 0 confirmed_inputs = [] # type: List[Address] gb_response = GetBalancesCommand(self.adapter)( addresses=[i.address for i in proposed_inputs], ) for i, balance in enumerate(gb_response.get('balances') or []): input_ = proposed_inputs[i] if balance > 0: available_to_spend += balance # Update the address balance from the API response, just in # case somebody tried to cheat. input_.balance = balance confirmed_inputs.append(input_) if available_to_spend < want_to_spend: raise with_context( exc=BadApiResponse( 'Insufficient balance; found {found}, need {need} ' '(``exc.context`` has more info).'.format( found=available_to_spend, need=want_to_spend, ), ), context={ 'available_to_spend': available_to_spend, 'confirmed_inputs': confirmed_inputs, 'request': request, 'want_to_spend': want_to_spend, }, ) bundle.add_inputs(confirmed_inputs) if bundle.balance < 0: if not change_address: change_address =\ GetNewAddressesCommand(self.adapter)(seed=seed)['addresses'][0] bundle.send_unspent_inputs_to(change_address) bundle.finalize() if confirmed_inputs: bundle.sign_inputs(KeyGenerator(seed)) else: bundle.finalize() return { 'trytes': bundle.as_tryte_strings(), }