def _execute(self, request): depth = request['depth'] # type: int min_weight_magnitude = request['minWeightMagnitude'] # type: int transaction = request['transaction'] # type: TransactionHash cc_response = CheckConsistencyCommand( self.adapter)(tails=[transaction]) if cc_response['state'] is False: raise BadApiResponse( 'Transaction {transaction} is not promotable. ' 'You should reattach first.'.format(transaction=transaction)) spam_transfer = ProposedTransaction( address=Address(b''), value=0, ) return SendTransferCommand(self.adapter)( seed=spam_transfer.address, depth=depth, transfers=[spam_transfer], minWeightMagnitude=min_weight_magnitude, reference=transaction, )
def setUp(self): super(SendTransferCommandTestCase, self).setUp() self.adapter = MockAdapter() self.command = SendTransferCommand(self.adapter)
class SendTransferRequestFilterTestCase(BaseFilterTestCase): filter_type = SendTransferCommand(MockAdapter()).get_request_filter skip_value_check = True def setUp(self): super(SendTransferRequestFilterTestCase, self).setUp() # Define some tryte sequences that we can reuse between tests. self.trytes1 = (b'TESTVALUEONE9DONTUSEINPRODUCTION99999JBW' b'GEC99GBXFFBCHAEJHLC9DX9EEPAI9ICVCKBX9FFII') self.trytes2 = (b'TESTVALUETWO9DONTUSEINPRODUCTION99999THZ' b'BODYHZM99IR9KOXLZXVUOJM9LQKCQJBWMTY999999') self.trytes3 = (b'TESTVALUETHREE9DONTUSEINPRODUCTIONG99999' b'GTQ9CSNUFPYW9MBQ9LFQJSORCF9LGTY9BWQFY9999') self.trytes4 = (b'TESTVALUEFOUR9DONTUSEINPRODUCTION99999ZQ' b'HOGCBZCOTZVZRFBEHQKHENBIZWDTUQXTOVWEXRIK9') self.transfer1 =\ ProposedTransaction( address = Address( b'TESTVALUEFIVE9DONTUSEINPRODUCTION99999MG' b'AAAHJDZ9BBG9U9R9XEOHCBVCLCWCCCCBQCQGG9WHK' ), value = 42, ) self.transfer2 =\ ProposedTransaction( address = Address( b'TESTVALUESIX9DONTUSEINPRODUCTION99999GGT' b'FODSHHELBDERDCDRBCINDCGQEI9NAWDJBC9TGPFME' ), value = 86, ) def test_pass_happy_path(self): """ Request is valid. """ request = { 'changeAddress': Address(self.trytes1), 'depth': 100, 'minWeightMagnitude': 18, 'seed': Seed(self.trytes2), 'inputs': [ Address(self.trytes3), Address(self.trytes4), ], 'transfers': [self.transfer1, self.transfer2], 'reference': TransactionHash(self.trytes1), 'securityLevel': AddressGenerator.DEFAULT_SECURITY_LEVEL, } filter_ = self._filter(request) self.assertFilterPasses(filter_) self.assertDictEqual(filter_.cleaned_data, request) def test_pass_compatible_types(self): """ Request contains values that can be converted to the expected types. """ filter_ = self._filter({ # Any TrytesCompatible values will work here. 'changeAddress': bytes(self.trytes1), 'seed': bytearray(self.trytes2), 'reference': bytes(self.trytes1), 'inputs': [ bytes(self.trytes3), bytearray(self.trytes4), ], # These values must have the correct type, however. 'transfers': [self.transfer1, self.transfer2], 'depth': 100, 'minWeightMagnitude': 18, 'securityLevel': None, }) self.assertFilterPasses(filter_) self.assertDictEqual( filter_.cleaned_data, { 'changeAddress': Address(self.trytes1), 'depth': 100, 'minWeightMagnitude': 18, 'seed': Seed(self.trytes2), 'reference': TransactionHash(self.trytes1), 'inputs': [ Address(self.trytes3), Address(self.trytes4), ], 'transfers': [self.transfer1, self.transfer2], 'securityLevel': AddressGenerator.DEFAULT_SECURITY_LEVEL }) def test_pass_optional_parameters_omitted(self): """ Request omits optional parameters. """ filter_ = self._filter({ 'depth': 100, 'minWeightMagnitude': 13, 'seed': Seed(self.trytes2), 'transfers': [self.transfer1, self.transfer2], }) self.assertFilterPasses(filter_) self.assertDictEqual( filter_.cleaned_data, { 'changeAddress': None, 'inputs': None, 'reference': None, 'depth': 100, 'minWeightMagnitude': 13, 'securityLevel': AddressGenerator.DEFAULT_SECURITY_LEVEL, 'seed': Seed(self.trytes2), 'transfers': [self.transfer1, self.transfer2], }) def test_fail_empty(self): """ Request is empty. """ self.assertFilterErrors( {}, { 'depth': [f.FilterMapper.CODE_MISSING_KEY], 'minWeightMagnitude': [f.FilterMapper.CODE_MISSING_KEY], 'seed': [f.FilterMapper.CODE_MISSING_KEY], 'transfers': [f.FilterMapper.CODE_MISSING_KEY], }, ) def test_fail_unexpected_parameters(self): """ Request contains unexpected parameters. """ self.assertFilterErrors( { 'depth': 100, 'minWeightMagnitude': 18, 'seed': Seed(self.trytes1), 'transfers': [self.transfer1], # Maybe he's not that smart; maybe he's like a worker bee who # only knows how to push buttons or something. 'foo': 'bar', }, { 'foo': [f.FilterMapper.CODE_EXTRA_KEY], }, ) def test_fail_seed_null(self): """ ``seed`` is null. """ self.assertFilterErrors( { 'seed': None, 'depth': 100, 'minWeightMagnitude': 18, 'transfers': [self.transfer1], }, { 'seed': [f.Required.CODE_EMPTY], }, ) def test_fail_seed_wrong_type(self): """ ``seed`` is not a TrytesCompatible value. """ self.assertFilterErrors( { 'seed': 42, 'depth': 100, 'minWeightMagnitude': 18, 'transfers': [self.transfer1], }, { 'seed': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_seed_not_trytes(self): """ ``seed`` contains invalid characters. """ self.assertFilterErrors( { 'seed': b'not valid; must contain only uppercase and "9"', 'depth': 100, 'minWeightMagnitude': 18, 'transfers': [self.transfer1], }, { 'seed': [Trytes.CODE_NOT_TRYTES], }, ) def test_fail_transfers_wrong_type(self): """ ``transfers`` is not an array. """ self.assertFilterErrors( { 'transfers': self.transfer1, 'depth': 100, 'minWeightMagnitude': 18, 'seed': Seed(self.trytes1), }, { 'transfers': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_transfers_empty(self): """ ``transfers`` is an array, but it is empty. """ self.assertFilterErrors( { 'transfers': [], 'depth': 100, 'minWeightMagnitude': 18, 'seed': Seed(self.trytes1), }, { 'transfers': [f.Required.CODE_EMPTY], }, ) def test_fail_transfers_contents_invalid(self): """ ``transfers`` is a non-empty array, but it contains invalid values. """ self.assertFilterErrors( { 'transfers': [ None, # This value is valid; just adding it to make sure the filter # doesn't cheat! ProposedTransaction(address=Address(self.trytes2), value=42), { 'address': Address(self.trytes2), 'value': 42 }, ], 'depth': 100, 'minWeightMagnitude': 18, 'seed': Seed(self.trytes1), }, { 'transfers.0': [f.Required.CODE_EMPTY], 'transfers.2': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_change_address_wrong_type(self): """ ``changeAddress`` is not a TrytesCompatible value. """ self.assertFilterErrors( { 'changeAddress': 42, 'depth': 100, 'minWeightMagnitude': 18, 'seed': Seed(self.trytes1), 'transfers': [self.transfer1], }, { 'changeAddress': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_change_address_not_trytes(self): """ ``changeAddress`` contains invalid characters. """ self.assertFilterErrors( { 'changeAddress': b'not valid; must contain only uppercase and "9"', 'depth': 100, 'minWeightMagnitude': 18, 'seed': Seed(self.trytes1), 'transfers': [self.transfer1], }, { 'changeAddress': [Trytes.CODE_NOT_TRYTES], }, ) def test_fail_inputs_wrong_type(self): """ ``inputs`` is not an array. """ self.assertFilterErrors( { # Must be an array, even if there's only one input. 'inputs': Address(self.trytes4), 'depth': 100, 'minWeightMagnitude': 18, 'seed': Seed(self.trytes1), 'transfers': [self.transfer1], }, { 'inputs': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_inputs_contents_invalid(self): """ ``inputs`` is a non-empty array, but it contains invalid values. """ self.assertFilterErrors( { 'inputs': [ 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.trytes4), 2130706433, b'9' * 82, ], 'depth': 100, 'minWeightMagnitude': 18, 'seed': Seed(self.trytes1), 'transfers': [self.transfer1], }, { 'inputs.0': [f.Required.CODE_EMPTY], 'inputs.1': [f.Type.CODE_WRONG_TYPE], 'inputs.2': [f.Required.CODE_EMPTY], 'inputs.3': [Trytes.CODE_NOT_TRYTES], 'inputs.5': [f.Type.CODE_WRONG_TYPE], 'inputs.6': [Trytes.CODE_WRONG_FORMAT], }, ) def test_fail_depth_null(self): """ ``depth`` is null. """ self.assertFilterErrors( { 'depth': None, 'minWeightMagnitude': 18, 'seed': Seed(self.trytes1), 'transfers': [self.transfer1], }, { 'depth': [f.Required.CODE_EMPTY], }, ) def test_fail_depth_string(self): """ ``depth`` is a string. """ self.assertFilterErrors( { # Too ambiguous; it must be an int. 'depth': '2', 'minWeightMagnitude': 18, 'seed': Seed(self.trytes1), 'transfers': [self.transfer1], }, { 'depth': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_depth_float(self): """ ``depth`` is a float. """ self.assertFilterErrors( { # Even with an empty fpart, floats are invalid. 'depth': 100.0, 'minWeightMagnitude': 18, 'seed': Seed(self.trytes1), 'transfers': [self.transfer1], }, { 'depth': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_depth_too_small(self): """ ``depth`` is < 1. """ self.assertFilterErrors( { 'depth': 0, 'minWeightMagnitude': 18, 'seed': Seed(self.trytes1), 'transfers': [self.transfer1], }, { 'depth': [f.Min.CODE_TOO_SMALL], }, ) def test_fail_min_weight_magnitude_null(self): """ ``minWeightMagnitude`` is null. """ self.assertFilterErrors( { 'minWeightMagnitude': None, 'depth': 100, 'seed': Seed(self.trytes1), 'transfers': [self.transfer1], }, { 'minWeightMagnitude': [f.Required.CODE_EMPTY], }, ) def test_fail_min_weight_magnitude_string(self): """ ``minWeightMagnitude`` is a string. """ self.assertFilterErrors( { # Nope; it's gotta be an int. 'minWeightMagnitude': '18', 'depth': 100, 'seed': Seed(self.trytes1), 'transfers': [self.transfer1], }, { 'minWeightMagnitude': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_min_weight_magnitude_float(self): """ ``minWeightMagnitude`` is a float. """ self.assertFilterErrors( { # Even with an empty fpart, floats are invalid. 'minWeightMagnitude': 18.0, 'depth': 100, 'seed': Seed(self.trytes1), 'transfers': [self.transfer1], }, { 'minWeightMagnitude': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_min_weight_magnitude_too_small(self): """ ``minWeightMagnitude`` is < 1. """ self.assertFilterErrors( { 'minWeightMagnitude': 0, 'depth': 100, 'seed': Seed(self.trytes1), 'transfers': [self.transfer1], }, { 'minWeightMagnitude': [f.Min.CODE_TOO_SMALL], }, ) def test_fail_reference_wrong_type(self): """ ``reference`` is not a TrytesCompatible value. """ self.assertFilterErrors( { 'reference': 42, 'seed': Seed(self.trytes1), 'depth': 100, 'minWeightMagnitude': 18, 'transfers': [self.transfer1], }, { 'reference': [f.Type.CODE_WRONG_TYPE], }, ) def test_fail_reference_not_trytes(self): """ ``reference`` contains invalid characters. """ self.assertFilterErrors( { 'reference': b'not valid; must contain only uppercase and "9"', 'seed': Seed(self.trytes1), 'depth': 100, 'minWeightMagnitude': 18, 'transfers': [self.transfer1], }, { 'reference': [Trytes.CODE_NOT_TRYTES], }, ) def test_fail_wrong_security_level(self): """ ``security_level`` is not one of integers 1, 2 or 3. """ self.assertFilterErrors( { 'depth': 100, 'minWeightMagnitude': 18, 'seed': Seed(self.trytes1), 'transfers': [self.transfer1], # Maybe he's not that smart; maybe he's like a worker bee who # only knows how to push buttons or something. 'securityLevel': 0, }, { 'securityLevel': [f.Min.CODE_TOO_SMALL], }, ) def test_fail_wrong_security_level_type(self): """ ``security_level`` is not one of integers 1, 2 or 3. """ self.assertFilterErrors( { 'depth': 100, 'minWeightMagnitude': 18, 'seed': Seed(self.trytes1), 'transfers': [self.transfer1], # Maybe he's not that smart; maybe he's like a worker bee who # only knows how to push buttons or something. 'securityLevel': "2", }, { 'securityLevel': [f.Type.CODE_WRONG_TYPE], }, )