Exemple #1
0
class ServerTestCase(unittest.TestCase):
    """
    Base class for test cases that need to call the server.

    It runs calls to actions through the server stack so they get middleware run
    (for things like request.metrics) and requests/responses run through a
    serializer cycle.
    """

    server_class = None
    server_settings = None

    def setUp(self):
        super(ServerTestCase, self).setUp()

        if self.server_class is None:
            raise RuntimeError('You must specify server_class in ServerTestCase subclasses')

        # Get settings based on Django mode
        if self.server_settings is not None:
            settings = self.server_settings
        else:
            if self.server_class.use_django:
                from django.conf import settings
                settings = settings.SOA_SERVER_SETTINGS
            else:
                settings_module = os.environ.get('PYSOA_SETTINGS_MODULE', None)
                if not settings_module:
                    self.fail('PYSOA_SETTINGS_MODULE environment variable must be set to run tests.')
                try:
                    thing = importlib.import_module(settings_module)
                    settings = thing.SOA_SERVER_SETTINGS
                except (ImportError, AttributeError) as e:
                    self.fail('Could not access {}.SOA_SERVER_SETTINGS: {}'.format(settings_module, e))

        self.client = Client(
            {
                self.server_class.service_name: {
                    'transport': {
                        'path': 'pysoa.common.transport.local:LocalClientTransport',
                        'kwargs': {
                            'server_class': self.server_class,
                            'server_settings': settings,
                        },
                    },
                },
            },
        )

    def call_action(self, action, body=None, service_name=None, **kwargs):
        # Using this enables tests that call the same action dozens of times to not have to code in the service name
        # for every single action call (but they still can by passing in `service_name`)
        return self.client.call_action(service_name or self.server_class.service_name, action, body=body, **kwargs)

    def assertActionRunsWithAndReturnErrors(self, action, body, **kwargs):
        try:
            self.call_action(action, body, **kwargs)
            # If we got here, it ran with no errors, so fail
            raise self.failureException('Action ran without any of the expected errors')
        except self.client.CallActionError as e:
            return e.actions[0].errors

    def assertActionRunsWithFieldErrors(self, action, body, field_errors, only=False, **kwargs):
        raised_errors = self.assertActionRunsWithAndReturnErrors(action, body, **kwargs)

        unexpected_errors = []
        missing_errors = []

        # Provide the flexibility for them to pass it a set or list of error codes, or a single code, per field
        for field, errors in six.iteritems(field_errors):
            if not isinstance(errors, set):
                if isinstance(errors, list):
                    field_errors[field] = set(errors)
                else:
                    field_errors[field] = {errors}

        # Go through all the errors returned by the action, mark any that are unexpected, remove any that match
        for error in raised_errors:
            if not getattr(error, 'field', None):
                unexpected_errors.append((error.code, error.message))
                continue

            if error.field not in field_errors:
                unexpected_errors.append({error.field: (error.code, error.message)})
                continue

            if error.code not in field_errors[error.field]:
                unexpected_errors.append({error.field: (error.code, error.message)})
                continue

            field_errors[error.field].remove(error.code)
            if not field_errors[error.field]:
                del field_errors[error.field]

        # Go through all the remaining expected errors that weren't matched
        for field, errors in six.iteritems(field_errors):
            for error in errors:
                missing_errors.append({field: error})

        error_msg = ''
        if missing_errors:
            error_msg = 'Expected field errors not found in response: {}'.format(str(missing_errors))

        if only and unexpected_errors:
            if error_msg:
                error_msg += '\n'
            error_msg += 'Unexpected errors found in response: {}'.format(str(unexpected_errors))

        if error_msg:
            # If we have any cause to error, do so
            raise self.failureException(error_msg)

    def assertActionRunsWithOnlyFieldErrors(self, action, body, field_errors, **kwargs):
        self.assertActionRunsWithFieldErrors(action, body, field_errors, only=True, **kwargs)

    def assertActionRunsWithErrorCodes(self, action, body, error_codes, only=False, **kwargs):
        raised_errors = self.assertActionRunsWithAndReturnErrors(action, body, **kwargs)

        if not isinstance(error_codes, set):
            if isinstance(error_codes, list):
                error_codes = set(error_codes)
            else:
                error_codes = {error_codes}

        unexpected_errors = []
        missing_errors = []

        # Go through all the errors returned by the action, mark any that are unexpected, remove any that match
        for error in raised_errors:
            if getattr(error, 'field', None):
                unexpected_errors.append({error.field: (error.code, error.message)})
                continue

            if error.code not in error_codes:
                unexpected_errors.append((error.code, error.message))
                continue

            error_codes.remove(error.code)

        # Go through all the remaining expected errors that weren't matched
        for error in error_codes:
            missing_errors.append(error)

        error_msg = ''
        if missing_errors:
            error_msg = 'Expected errors not found in response: {}'.format(str(missing_errors))

        if only and unexpected_errors:
            if error_msg:
                error_msg += '\n'
            error_msg += 'Unexpected errors found in response: {}'.format(str(unexpected_errors))

        if error_msg:
            # If we have any cause to error, do so
            raise self.failureException(error_msg)

    def assertActionRunsWithOnlyErrorCodes(self, action, body, error_codes, **kwargs):
        self.assertActionRunsWithErrorCodes(action, body, error_codes, only=True, **kwargs)
Exemple #2
0
from pysoa.client import Client

from settings import SOA_CLIENT_SETTINGS

if __name__ == '__main__':
    client = Client({'example': SOA_CLIENT_SETTINGS})
    action_response = client.call_action('example', 'square', {'number': 42})
    print(action_response)
class TestStubAction(ServerTestCase):
    server_class = _TestServiceServer
    server_settings = {}

    def setUp(self):
        super(TestStubAction, self).setUp()

        self.secondary_stub_client = Client(_secondary_stub_client_settings)

    @stub_action('test_service', 'test_action_1')
    def test_one_stub_as_decorator(self, stub_test_action_1):
        stub_test_action_1.return_value = {'value': 1}

        response = self.client.call_action('test_service', 'test_action_1')
        self.assertEqual({'value': 1}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)
        stub_test_action_1.assert_called_once_with({})

    def test_one_stub_as_context_manager(self):
        with stub_action('test_service',
                         'test_action_1') as stub_test_action_1:
            stub_test_action_1.return_value = {'value': 1}

            response = self.client.call_action('test_service', 'test_action_1')

        self.assertEqual({'value': 1}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)
        stub_test_action_1.assert_called_once_with({})

    @stub_action('test_service', 'test_action_1', body={'value': 1})
    def test_one_stub_as_decorator_with_body(self, stub_test_action_1):
        response = self.client.call_action('test_service', 'test_action_1')
        self.assertEqual({'value': 1}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)
        stub_test_action_1.assert_called_once_with({})

    def test_one_stub_as_context_manager_with_body(self):
        with stub_action('test_service', 'test_action_1',
                         body={'value': 1}) as stub_test_action_1:
            response = self.client.call_action('test_service', 'test_action_1')

        self.assertEqual({'value': 1}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)
        stub_test_action_1.assert_called_once_with({})

    @stub_action('test_service', 'test_action_2')
    @stub_action('test_service', 'test_action_1')
    def test_two_stubs_same_service_as_decorator(self, stub_test_action_1,
                                                 stub_test_action_2):
        stub_test_action_1.return_value = {'value': 1}
        stub_test_action_2.return_value = {'another_value': 2}

        response = self.client.call_action('test_service', 'test_action_1')
        self.assertEqual({'value': 1}, response.body)

        response = self.client.call_action('test_service', 'test_action_2',
                                           {'input_attribute': True})
        self.assertEqual({'another_value': 2}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)
        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'input_attribute': True},
                         stub_test_action_2.call_body)
        stub_test_action_1.assert_called_once_with({})
        stub_test_action_2.assert_called_once_with({'input_attribute': True})

    @stub_action('test_service', 'test_action_2')
    @stub_action('test_service', 'test_action_1')
    def test_two_stubs_same_service_as_decorator_multiple_calls_to_one(
            self, stub_test_action_1, stub_test_action_2):
        stub_test_action_1.return_value = {'value': 1}
        stub_test_action_2.side_effect = ({
            'another_value': 2
        }, {
            'third_value': 3
        })

        response = self.client.call_action('test_service', 'test_action_1')
        self.assertEqual({'value': 1}, response.body)

        response = self.client.call_action('test_service', 'test_action_2',
                                           {'input_attribute': True})
        self.assertEqual({'another_value': 2}, response.body)

        response = self.client.call_action('test_service', 'test_action_2',
                                           {'another_attribute': False})
        self.assertEqual({'third_value': 3}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)
        self.assertEqual(2, stub_test_action_2.call_count)
        self.assertEqual({'another_attribute': False},
                         stub_test_action_2.call_body)
        self.assertEqual(({
            'input_attribute': True
        }, {
            'another_attribute': False
        }), stub_test_action_2.call_bodies)
        stub_test_action_1.assert_called_once_with({})
        stub_test_action_2.assert_has_calls([
            mock.call({'input_attribute': True}),
            mock.call({'another_attribute': False}),
        ])

    @stub_action('test_service', 'test_action_1')
    def test_two_stubs_same_service_split(self, stub_test_action_1):
        stub_test_action_1.return_value = {'value': 1}

        with stub_action('test_service',
                         'test_action_2') as stub_test_action_2:
            stub_test_action_2.return_value = {'another_value': 2}

            response = self.client.call_action('test_service', 'test_action_1')
            self.assertEqual({'value': 1}, response.body)

            response = self.client.call_action('test_service', 'test_action_2',
                                               {'input_attribute': True})
            self.assertEqual({'another_value': 2}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)
        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'input_attribute': True},
                         stub_test_action_2.call_body)

    @stub_action('test_another_service', 'test_action_2')
    @stub_action('test_service', 'test_action_1')
    def test_two_stubs_different_services_as_decorator(self,
                                                       stub_test_action_1,
                                                       stub_test_action_2):
        stub_test_action_1.return_value = {'value': 1}
        stub_test_action_2.return_value = {'another_value': 2}

        response = self.client.call_action('test_service', 'test_action_1')
        self.assertEqual({'value': 1}, response.body)

        response = self.client.call_action('test_another_service',
                                           'test_action_2',
                                           {'input_attribute': True})
        self.assertEqual({'another_value': 2}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)
        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'input_attribute': True},
                         stub_test_action_2.call_body)

    @stub_action('test_service', 'test_action_1')
    def test_two_stubs_different_services_split(self, stub_test_action_1):
        stub_test_action_1.return_value = {'value': 1}

        with stub_action('test_another_service',
                         'test_action_2') as stub_test_action_2:
            stub_test_action_2.return_value = {'another_value': 2}

            response = self.client.call_action('test_service', 'test_action_1')
            self.assertEqual({'value': 1}, response.body)

            response = self.client.call_action('test_another_service',
                                               'test_action_2',
                                               {'input_attribute': True})
            self.assertEqual({'another_value': 2}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)
        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'input_attribute': True},
                         stub_test_action_2.call_body)

    @stub_action('test_service', 'test_action_1', body={'value': 1})
    def test_one_stub_as_decorator_with_real_call_handled(
            self, stub_test_action_1):
        response = self.client.call_action('test_service', 'test_action_1')
        self.assertEqual(response.body, {'value': 1})

        response = self.secondary_stub_client.call_action('cat', 'meow')
        self.assertEqual({'type': 'squeak'}, response.body)

        response = self.secondary_stub_client.call_action('dog', 'bark')
        self.assertEqual({'sound': 'woof'}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)

    def test_one_stub_as_context_manager_with_real_call_handled(self):
        with stub_action('test_service', 'test_action_1',
                         body={'value': 1}) as stub_test_action_1:
            response = self.client.call_action('test_service', 'test_action_1')
            self.assertEqual(response.body, {'value': 1})

            response = self.secondary_stub_client.call_action('cat', 'meow')
            self.assertEqual({'type': 'squeak'}, response.body)

            response = self.secondary_stub_client.call_action('dog', 'bark')
            self.assertEqual({'sound': 'woof'}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)

    @stub_action('test_service', 'test_action_2')
    @mock.patch(__name__ + '._test_function', return_value=3)
    def test_as_decorator_with_patch_before(self, mock_randint,
                                            stub_test_action_2):
        stub_test_action_2.return_value = {'value': 99}

        response = self.client.call_actions(
            'test_service',
            [
                ActionRequest(action='test_action_1'),
                ActionRequest(action='test_action_2')
            ],
        )

        self.assertEqual(2, len(response.actions))
        self.assertEqual({'value': 3}, response.actions[0].body)
        self.assertEqual({'value': 99}, response.actions[1].body)

        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({}, stub_test_action_2.call_body)
        mock_randint.assert_called_once_with(0, 99)

    @mock.patch(__name__ + '._test_function', return_value=7)
    @stub_action('test_service', 'test_action_2')
    def test_as_decorator_with_patch_after(self, stub_test_action_2,
                                           mock_randint):
        stub_test_action_2.side_effect = ({'value': 122}, {'also': 157})

        response = self.client.call_actions(
            'test_service',
            [{
                'action': 'test_action_1'
            }, {
                'action': 'test_action_2'
            }, {
                'action': 'test_action_2'
            }],
        )

        self.assertEqual(3, len(response.actions))
        self.assertEqual({'value': 7}, response.actions[0].body)
        self.assertEqual({'value': 122}, response.actions[1].body)
        self.assertEqual({'also': 157}, response.actions[2].body)

        self.assertEqual(2, stub_test_action_2.call_count)
        self.assertEqual(({}, {}), stub_test_action_2.call_bodies)
        stub_test_action_2.assert_has_calls([mock.call({}), mock.call({})])
        mock_randint.assert_called_once_with(0, 99)

    def test_using_start_stop(self):
        action_stubber = stub_action('test_service', 'test_action_1')
        stubbed_action = action_stubber.start()
        stubbed_action.return_value = {'what about': 'this'}

        response = self.client.call_action('test_service', 'test_action_1', {
            'burton': 'guster',
            'sean': 'spencer'
        })
        self.assertEqual({'what about': 'this'}, response.body)

        self.assertEqual(1, stubbed_action.call_count)
        self.assertEqual({
            'burton': 'guster',
            'sean': 'spencer'
        }, stubbed_action.call_body)
        stubbed_action.assert_called_once_with({
            'burton': 'guster',
            'sean': 'spencer'
        })
        action_stubber.stop()

    @stub_action('test_service',
                 'test_action_2',
                 errors=[
                     {
                         'code': 'BAD_FOO',
                         'field': 'foo',
                         'message': 'Nope'
                     },
                 ])
    def test_mock_action_with_error_raises_exception(self, stub_test_action_2):
        with self.assertRaises(Client.CallActionError) as e:
            self.client.call_action('test_service', 'test_action_2',
                                    {'a': 'body'})

        self.assertEqual('BAD_FOO', e.exception.actions[0].errors[0].code)
        self.assertEqual('foo', e.exception.actions[0].errors[0].field)
        self.assertEqual('Nope', e.exception.actions[0].errors[0].message)

        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'a': 'body'}, stub_test_action_2.call_body)
        stub_test_action_2.assert_called_once_with({'a': 'body'})

    @stub_action('test_service', 'test_action_2')
    def test_mock_action_with_error_side_effect_raises_exception(
            self, stub_test_action_2):
        stub_test_action_2.side_effect = ActionError(
            errors=[Error(code='BAR_BAD', field='bar', message='Uh-uh')])

        with self.assertRaises(Client.CallActionError) as e:
            self.client.call_action('test_service', 'test_action_2',
                                    {'a': 'body'})

        self.assertEqual('BAR_BAD', e.exception.actions[0].errors[0].code)
        self.assertEqual('bar', e.exception.actions[0].errors[0].field)
        self.assertEqual('Uh-uh', e.exception.actions[0].errors[0].message)

        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'a': 'body'}, stub_test_action_2.call_body)
        stub_test_action_2.assert_called_once_with({'a': 'body'})

    @stub_action('test_service', 'test_action_2')
    def test_mock_action_with_job_error_side_effect_raises_job_error_exception(
            self, stub_test_action_2):
        stub_test_action_2.side_effect = JobError(
            errors=[Error(code='BAR_BAD_JOB', message='Uh-uh job')])

        with self.assertRaises(Client.JobError) as e:
            self.client.call_action('test_service', 'test_action_2',
                                    {'a': 'body'})

        self.assertEqual('BAR_BAD_JOB', e.exception.errors[0].code)
        self.assertIsNone(e.exception.errors[0].field)
        self.assertEqual('Uh-uh job', e.exception.errors[0].message)

        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'a': 'body'}, stub_test_action_2.call_body)
        stub_test_action_2.assert_called_once_with({'a': 'body'})

    @stub_action('test_service', 'test_action_2')
    def test_mock_action_with_job_error_response_raises_job_error_exception(
            self, stub_test_action_2):
        stub_test_action_2.return_value = JobResponse(
            errors=[Error(code='BAR_BAD_JOB', message='Uh-uh job')])

        with self.assertRaises(Client.JobError) as e:
            self.client.call_action('test_service', 'test_action_2',
                                    {'a': 'body'})

        self.assertEqual('BAR_BAD_JOB', e.exception.errors[0].code)
        self.assertIsNone(e.exception.errors[0].field)
        self.assertEqual('Uh-uh job', e.exception.errors[0].message)

        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'a': 'body'}, stub_test_action_2.call_body)
        stub_test_action_2.assert_called_once_with({'a': 'body'})

    @stub_action('test_service',
                 'test_action_2',
                 errors=[
                     {
                         'code': 'INVALID_BAR',
                         'message': 'A bad message'
                     },
                 ])
    def test_multiple_actions_stop_on_error(self, stub_test_action_2):
        response = self.client.call_actions(
            'test_service',
            [
                ActionRequest(action='test_action_1'),
                ActionRequest(action='test_action_2'),
                ActionRequest(action='test_action_1'),
            ],
            raise_action_errors=False,
        )

        # Called 3 actions, but expected to stop after the error in the second action
        self.assertEqual(2, len(response.actions))
        self.assertEqual('INVALID_BAR', response.actions[1].errors[0].code)
        self.assertEqual('A bad message',
                         response.actions[1].errors[0].message)
        self.assertTrue(stub_test_action_2.called)

    @stub_action('test_service',
                 'test_action_2',
                 errors=[
                     {
                         'code': 'MISSING_BAZ',
                         'field': 'entity_id',
                         'message': 'Your entity ID was missing'
                     },
                 ])
    def test_multiple_actions_continue_on_error(self, mock_test_action_2):
        response = self.client.call_actions(
            'test_service',
            [{
                'action': 'test_action_1'
            }, {
                'action': 'test_action_2'
            }, {
                'action': 'test_action_1'
            }],
            raise_action_errors=False,
            continue_on_error=True,
        )

        # Called 3 actions, and expected all three of them to be called, even with the interrupting error
        self.assertEqual(3, len(response.actions))
        self.assertEqual('MISSING_BAZ', response.actions[1].errors[0].code)
        self.assertEqual('entity_id', response.actions[1].errors[0].field)
        self.assertEqual('Your entity ID was missing',
                         response.actions[1].errors[0].message)
        self.assertTrue(mock_test_action_2.called)

    @stub_action('test_service', 'test_action_2', body={'three': 'four'})
    @stub_action('test_service', 'test_action_1', body={'one': 'two'})
    def test_two_stubs_with_parallel_calls_all_stubbed(self,
                                                       stub_test_action_1,
                                                       stub_test_action_2):
        job_responses = self.client.call_jobs_parallel([
            {
                'service_name': 'test_service',
                'actions': [{
                    'action': 'test_action_1',
                    'body': {
                        'a': 'b'
                    }
                }]
            },
            {
                'service_name': 'test_service',
                'actions': [{
                    'action': 'test_action_2',
                    'body': {
                        'c': 'd'
                    }
                }]
            },
        ], )

        self.assertIsNotNone(job_responses)
        self.assertEqual(2, len(job_responses))
        self.assertEqual(1, len(job_responses[0].actions))
        self.assertEqual({'one': 'two'}, job_responses[0].actions[0].body)
        self.assertEqual(1, len(job_responses[1].actions))
        self.assertEqual({'three': 'four'}, job_responses[1].actions[0].body)

        stub_test_action_1.assert_called_once_with({'a': 'b'})
        stub_test_action_2.assert_called_once_with({'c': 'd'})

    @stub_action('test_service', 'test_action_2')
    @mock.patch(__name__ + '._test_function')
    def test_one_stub_with_parallel_calls(self, mock_randint,
                                          stub_test_action_2):
        mock_randint.side_effect = (42, 17, 31)
        stub_test_action_2.return_value = {'concert': 'tickets'}

        job_responses = self.client.call_jobs_parallel([
            {
                'service_name': 'test_service',
                'actions': [{
                    'action': 'test_action_1'
                }]
            },
            {
                'service_name':
                'test_service',
                'actions': [
                    {
                        'action': 'test_action_2',
                        'body': {
                            'slide': 'rule'
                        }
                    },
                    {
                        'action': 'test_action_1'
                    },
                ]
            },
            {
                'service_name': 'test_service',
                'actions': [{
                    'action': 'test_action_1'
                }]
            },
        ], )

        self.assertIsNotNone(job_responses)
        self.assertEqual(3, len(job_responses))
        self.assertEqual(1, len(job_responses[0].actions))
        self.assertEqual({'value': 42}, job_responses[0].actions[0].body)
        self.assertEqual(2, len(job_responses[1].actions))
        self.assertEqual({'concert': 'tickets'},
                         job_responses[1].actions[0].body)
        self.assertEqual({'value': 17}, job_responses[1].actions[1].body)
        self.assertEqual(1, len(job_responses[2].actions))
        self.assertEqual({'value': 31}, job_responses[2].actions[0].body)

        stub_test_action_2.assert_called_once_with({'slide': 'rule'})

    @stub_action('test_service', 'test_action_2')
    @stub_action('test_service', 'test_action_1')
    def test_two_stubs_with_parallel_calls(self, stub_test_action_1,
                                           stub_test_action_2):
        stub_test_action_1.return_value = {'value': 1}
        stub_test_action_2.return_value = {'another_value': 2}

        job_responses = Client(
            dict(self.client.config,
                 **_secondary_stub_client_settings)).call_jobs_parallel(
                     [
                         {
                             'service_name':
                             'test_service',
                             'actions': [
                                 {
                                     'action': 'test_action_1',
                                     'body': {
                                         'input_attribute': True
                                     }
                                 },
                                 {
                                     'action': 'test_action_2',
                                     'body': {
                                         'another_variable': 'Cool'
                                     }
                                 },
                             ]
                         },
                         {
                             'service_name': 'cat',
                             'actions': [{
                                 'action': 'meow'
                             }]
                         },
                         {
                             'service_name': 'dog',
                             'actions': [{
                                 'action': 'bark'
                             }]
                         },
                         {
                             'service_name': 'test_service',
                             'actions': [{
                                 'action': 'does_not_exist'
                             }]
                         },
                     ],
                     raise_action_errors=False,
                 )

        self.assertIsNotNone(job_responses)
        self.assertEqual(4, len(job_responses))
        self.assertEqual(2, len(job_responses[0].actions))
        self.assertEqual({'value': 1}, job_responses[0].actions[0].body)
        self.assertEqual({'another_value': 2},
                         job_responses[0].actions[1].body)
        self.assertEqual(1, len(job_responses[1].actions))
        self.assertEqual({'type': 'squeak'}, job_responses[1].actions[0].body)
        self.assertEqual(1, len(job_responses[2].actions))
        self.assertEqual({'sound': 'woof'}, job_responses[2].actions[0].body)
        self.assertEqual(1, len(job_responses[3].actions))
        self.assertEqual([
            Error(
                code='UNKNOWN',
                message=
                'The action "does_not_exist" was not found on this server.',
                field='action',
            )
        ], job_responses[3].actions[0].errors)

        stub_test_action_1.assert_called_once_with({'input_attribute': True})
        stub_test_action_2.assert_called_once_with(
            {'another_variable': 'Cool'})

    @stub_action('test_service', 'test_action_2', body={'three': 'four'})
    @stub_action('test_service', 'test_action_1')
    def test_two_stubs_with_parallel_calls_and_job_response_errors_raised(
            self, stub_test_action_1, stub_test_action_2):
        stub_test_action_1.return_value = JobResponse(
            errors=[Error(code='BAD_JOB', message='You are a bad job')])

        with self.assertRaises(self.client.JobError) as error_context:
            self.client.call_jobs_parallel([
                {
                    'service_name': 'test_service',
                    'actions': [{
                        'action': 'test_action_1',
                        'body': {
                            'a': 'b'
                        }
                    }]
                },
                {
                    'service_name': 'test_service',
                    'actions': [{
                        'action': 'test_action_2',
                        'body': {
                            'c': 'd'
                        }
                    }]
                },
            ], )

        self.assertEqual([Error(code='BAD_JOB', message='You are a bad job')],
                         error_context.exception.errors)

        stub_test_action_1.assert_called_once_with({'a': 'b'})
        stub_test_action_2.assert_called_once_with({'c': 'd'})

    @stub_action('test_service', 'test_action_2', body={'three': 'four'})
    @stub_action('test_service', 'test_action_1')
    def test_two_stubs_with_parallel_calls_and_job_errors_not_raised(
            self, stub_test_action_1, stub_test_action_2):
        stub_test_action_1.side_effect = JobError(
            errors=[Error(code='BAD_JOB', message='You are a bad job')])

        job_responses = self.client.call_jobs_parallel(
            [
                {
                    'service_name': 'test_service',
                    'actions': [{
                        'action': 'test_action_1',
                        'body': {
                            'a': 'b'
                        }
                    }]
                },
                {
                    'service_name': 'test_service',
                    'actions': [{
                        'action': 'test_action_2',
                        'body': {
                            'c': 'd'
                        }
                    }]
                },
            ],
            raise_job_errors=False,
        )

        self.assertIsNotNone(job_responses)
        self.assertEqual(2, len(job_responses))
        self.assertEqual(0, len(job_responses[0].actions))
        self.assertEqual([Error(code='BAD_JOB', message='You are a bad job')],
                         job_responses[0].errors)
        self.assertEqual(1, len(job_responses[1].actions))
        self.assertEqual({'three': 'four'}, job_responses[1].actions[0].body)

        stub_test_action_1.assert_called_once_with({'a': 'b'})
        stub_test_action_2.assert_called_once_with({'c': 'd'})

    @stub_action('test_service', 'test_action_2', body={'three': 'four'})
    @stub_action('test_service', 'test_action_1')
    def test_two_stubs_with_parallel_calls_and_action_errors_raised(
            self, stub_test_action_1, stub_test_action_2):
        stub_test_action_1.side_effect = ActionError(
            errors=[Error(code='BAD_ACTION', message='You are a bad actor')])

        with self.assertRaises(self.client.CallActionError) as error_context:
            self.client.call_jobs_parallel([
                {
                    'service_name': 'test_service',
                    'actions': [{
                        'action': 'test_action_1',
                        'body': {
                            'a': 'b'
                        }
                    }]
                },
                {
                    'service_name': 'test_service',
                    'actions': [{
                        'action': 'test_action_2',
                        'body': {
                            'c': 'd'
                        }
                    }]
                },
            ], )

        self.assertEqual(
            [Error(code='BAD_ACTION', message='You are a bad actor')],
            error_context.exception.actions[0].errors,
        )

        stub_test_action_1.assert_called_once_with({'a': 'b'})
        stub_test_action_2.assert_called_once_with({'c': 'd'})

    @stub_action('test_service', 'test_action_2', body={'three': 'four'})
    @stub_action('test_service', 'test_action_1')
    def test_two_stubs_with_parallel_calls_and_action_response_errors_not_raised(
        self,
        stub_test_action_1,
        stub_test_action_2,
    ):
        stub_test_action_1.return_value = ActionResponse(
            action='test_action_1',
            errors=[Error(code='BAD_ACTION', message='You are a bad actor')],
        )

        job_responses = self.client.call_jobs_parallel(
            [
                {
                    'service_name': 'test_service',
                    'actions': [{
                        'action': 'test_action_1',
                        'body': {
                            'a': 'b'
                        }
                    }]
                },
                {
                    'service_name': 'test_service',
                    'actions': [{
                        'action': 'test_action_2',
                        'body': {
                            'c': 'd'
                        }
                    }]
                },
            ],
            raise_action_errors=False,
        )

        self.assertIsNotNone(job_responses)
        self.assertEqual(2, len(job_responses))
        self.assertEqual(1, len(job_responses[0].actions))
        self.assertEqual(
            [Error(code='BAD_ACTION', message='You are a bad actor')],
            job_responses[0].actions[0].errors)
        self.assertEqual(1, len(job_responses[1].actions))
        self.assertEqual({'three': 'four'}, job_responses[1].actions[0].body)

        stub_test_action_1.assert_called_once_with({'a': 'b'})
        stub_test_action_2.assert_called_once_with({'c': 'd'})

    @stub_action('test_service', 'test_action_1', body={'food': 'chicken'})
    def test_send_receive_one_stub_simple(self, stub_test_action_1):
        request_id = self.client.send_request('test_service', [{
            'action': 'test_action_1',
            'body': {
                'menu': 'look'
            }
        }])

        self.assertIsNotNone(request_id)

        responses = list(self.client.get_all_responses('test_service'))
        self.assertEqual(1, len(responses))

        received_request_id, response = responses[0]
        self.assertEqual(request_id, received_request_id)
        self.assertIsNotNone(response)
        self.assertEqual([], response.errors)
        self.assertEqual(1, len(response.actions))
        self.assertEqual([], response.actions[0].errors)
        self.assertEqual({'food': 'chicken'}, response.actions[0].body)

        stub_test_action_1.assert_called_once_with({'menu': 'look'})

    @stub_action('test_service', 'test_action_1')
    def test_send_receive_one_stub_multiple_calls(self, stub_test_action_1):
        stub_test_action_1.side_effect = ({
            'look': 'menu'
        }, {
            'pepperoni': 'pizza'
        }, {
            'cheese': 'pizza'
        })

        request_id1 = self.client.send_request('test_service', [
            {
                'action': 'test_action_1',
                'body': {
                    'menu': 'look'
                }
            },
            {
                'action': 'test_action_1',
                'body': {
                    'pizza': 'pepperoni'
                }
            },
        ])
        request_id2 = self.client.send_request('test_service', [
            {
                'action': 'test_action_1',
                'body': {
                    'pizza': 'cheese'
                }
            },
        ])

        self.assertIsNotNone(request_id1)
        self.assertIsNotNone(request_id2)

        responses = list(self.client.get_all_responses('test_service'))
        self.assertEqual(2, len(responses))

        response_dict = {k: v for k, v in responses}
        self.assertIn(request_id1, response_dict)
        self.assertIn(request_id2, response_dict)

        response = response_dict[request_id1]
        self.assertIsNotNone(response)
        self.assertEqual([], response.errors)
        self.assertEqual(2, len(response.actions))
        self.assertEqual([], response.actions[0].errors)
        self.assertEqual({'look': 'menu'}, response.actions[0].body)
        self.assertEqual({'pepperoni': 'pizza'}, response.actions[1].body)

        response = response_dict[request_id2]
        self.assertIsNotNone(response)
        self.assertEqual([], response.errors)
        self.assertEqual(1, len(response.actions))
        self.assertEqual([], response.actions[0].errors)
        self.assertEqual({'cheese': 'pizza'}, response.actions[0].body)

        stub_test_action_1.assert_has_calls(
            [
                mock.call({'menu': 'look'}),
                mock.call({'pizza': 'pepperoni'}),
                mock.call({'pizza': 'cheese'}),
            ],
            any_order=True,
        )

    @stub_action('test_service', 'test_action_1')
    def test_send_receive_one_stub_one_real_call_mixture(
            self, stub_test_action_1):
        stub_test_action_1.side_effect = (
            ActionResponse(action='does not matter', body={'look': 'menu'}),
            ActionResponse(action='no',
                           errors=[
                               Error(code='WEIRD',
                                     field='pizza',
                                     message='Weird error about pizza')
                           ]),
            ActionError(errors=[Error(code='COOL', message='Another error')]),
        )

        request_id1 = self.client.send_request(
            'test_service',
            [
                {
                    'action': 'test_action_1',
                    'body': {
                        'menu': 'look'
                    }
                },
                {
                    'action': 'test_action_2'
                },
                {
                    'action': 'test_action_1',
                    'body': {
                        'pizza': 'pepperoni'
                    }
                },
                {
                    'action': 'test_action_2'
                },
                {
                    'action': 'test_action_2'
                },
            ],
            continue_on_error=True,
        )
        request_id2 = self.client.send_request('test_service', [
            {
                'action': 'test_action_1',
                'body': {
                    'pizza': 'cheese'
                }
            },
        ])
        request_id3 = self.client.send_request('test_service', [
            {
                'action': 'test_action_2'
            },
        ])

        self.assertIsNotNone(request_id1)
        self.assertIsNotNone(request_id2)
        self.assertIsNotNone(request_id3)

        responses = list(self.client.get_all_responses('test_service'))
        self.assertEqual(3, len(responses))

        response_dict = {k: v for k, v in responses}
        self.assertIn(request_id1, response_dict)
        self.assertIn(request_id2, response_dict)
        self.assertIn(request_id3, response_dict)

        response = response_dict[request_id1]
        self.assertIsNotNone(response)
        self.assertEqual([], response.errors)
        self.assertEqual(5, len(response.actions))
        self.assertEqual([], response.actions[0].errors)
        self.assertEqual({'look': 'menu'}, response.actions[0].body)
        self.assertEqual([], response.actions[1].errors)
        self.assertEqual({'value': 0}, response.actions[1].body)
        self.assertEqual(
            [
                Error(code='WEIRD',
                      field='pizza',
                      message='Weird error about pizza')
            ],
            response.actions[2].errors,
        )
        self.assertEqual([], response.actions[3].errors)
        self.assertEqual({'value': 0}, response.actions[3].body)
        self.assertEqual([], response.actions[4].errors)
        self.assertEqual({'value': 0}, response.actions[4].body)

        response = response_dict[request_id2]
        self.assertIsNotNone(response)
        self.assertEqual([], response.errors)
        self.assertEqual(1, len(response.actions))
        self.assertEqual([Error(code='COOL', message='Another error')],
                         response.actions[0].errors)

        response = response_dict[request_id3]
        self.assertIsNotNone(response)
        self.assertEqual([], response.errors)
        self.assertEqual(1, len(response.actions))
        self.assertEqual([], response.actions[0].errors)
        self.assertEqual({'value': 0}, response.actions[0].body)

        stub_test_action_1.assert_has_calls(
            [
                mock.call({'menu': 'look'}),
                mock.call({'pizza': 'pepperoni'}),
                mock.call({'pizza': 'cheese'}),
            ],
            any_order=True,
        )
class TestStubActionAsDecoratedClass(ServerTestCase):
    server_class = _TestServiceServer
    server_settings = {}

    def setUp(self):
        super(TestStubActionAsDecoratedClass, self).setUp()

        self.secondary_stub_client = Client(_secondary_stub_client_settings)

    def test_works_as_expected(self, stub_test_action_2):
        stub_test_action_2.return_value = {'brown': 'cow'}

        response = self.client.call_action('test_service', 'test_action_2',
                                           {'how': 'now'})
        self.assertEqual({'brown': 'cow'}, response.body)

        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'how': 'now'}, stub_test_action_2.call_body)
        stub_test_action_2.assert_called_once_with({'how': 'now'})

    @mock.patch(__name__ + '._test_function', return_value=7)
    def test_works_also_with_patch(self, mock_randint, stub_test_action_2):
        stub_test_action_2.return_value = {'brown': 'cow'}

        response = self.client.call_action('test_service', 'test_action_2',
                                           {'how': 'now'})
        self.assertEqual({'brown': 'cow'}, response.body)

        response = self.client.call_action('test_service', 'test_action_1')
        self.assertEqual({'value': 7}, response.body)

        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'how': 'now'}, stub_test_action_2.call_body)
        stub_test_action_2.assert_called_once_with({'how': 'now'})

        mock_randint.assert_called_once_with(0, 99)

    @stub_action('test_service', 'test_action_1', body={'christmas': 'tree'})
    def test_works_with_other_action_stubbed_as_decorator(
            self, stub_test_action_1, stub_test_action_2):
        stub_test_action_2.return_value = {'brown': 'cow'}

        response = self.client.call_action('test_service', 'test_action_1',
                                           {'which': 'decoration'})
        self.assertEqual({'christmas': 'tree'}, response.body)

        response = self.client.call_action('test_service', 'test_action_2',
                                           {'how': 'now'})
        self.assertEqual({'brown': 'cow'}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({'which': 'decoration'}, stub_test_action_1.call_body)
        stub_test_action_1.assert_called_once_with({'which': 'decoration'})

        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'how': 'now'}, stub_test_action_2.call_body)
        stub_test_action_2.assert_called_once_with({'how': 'now'})

    def test_works_with_other_action_stubbed_as_context_manager(
            self, stub_test_action_2):
        stub_test_action_2.return_value = {'brown': 'cow'}

        with stub_action('test_service',
                         'test_action_1',
                         body={'christmas': 'tree'}) as stub_test_action_1:
            response = self.client.call_action('test_service', 'test_action_1',
                                               {'which': 'decoration'})
            self.assertEqual({'christmas': 'tree'}, response.body)

            response = self.client.call_action('test_service', 'test_action_2',
                                               {'how': 'now'})
            self.assertEqual({'brown': 'cow'}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({'which': 'decoration'}, stub_test_action_1.call_body)
        stub_test_action_1.assert_called_once_with({'which': 'decoration'})

        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'how': 'now'}, stub_test_action_2.call_body)
        stub_test_action_2.assert_called_once_with({'how': 'now'})

    def test_works_with_other_services_called(self, stub_test_action_2):
        stub_test_action_2.return_value = {'brown': 'cow'}

        response = self.client.call_action('test_service', 'test_action_2',
                                           {'how': 'now'})
        self.assertEqual({'brown': 'cow'}, response.body)

        response = self.secondary_stub_client.call_action('cat', 'meow')
        self.assertEqual({'type': 'squeak'}, response.body)

        response = self.secondary_stub_client.call_action('dog', 'bark')
        self.assertEqual({'sound': 'woof'}, response.body)

        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'how': 'now'}, stub_test_action_2.call_body)
        stub_test_action_2.assert_called_once_with({'how': 'now'})
Exemple #5
0
class TestStubAction(ServerTestCase):
    server_class = _TestServiceServer
    server_settings = {}

    def setUp(self):
        super(TestStubAction, self).setUp()

        self.secondary_stub_client = Client(_secondary_stub_client_settings)

    @stub_action('test_service', 'test_action_1')
    def test_one_stub_as_decorator(self, stub_test_action_1):
        stub_test_action_1.return_value = {'value': 1}

        response = self.client.call_action('test_service', 'test_action_1')
        self.assertEqual({'value': 1}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)
        stub_test_action_1.assert_called_once_with({})

    def test_one_stub_as_context_manager(self):
        with stub_action('test_service',
                         'test_action_1') as stub_test_action_1:
            stub_test_action_1.return_value = {'value': 1}

            response = self.client.call_action('test_service', 'test_action_1')

        self.assertEqual({'value': 1}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)
        stub_test_action_1.assert_called_once_with({})

    @stub_action('test_service', 'test_action_1', body={'value': 1})
    def test_one_stub_as_decorator_with_body(self, stub_test_action_1):
        response = self.client.call_action('test_service', 'test_action_1')
        self.assertEqual({'value': 1}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)
        stub_test_action_1.assert_called_once_with({})

    def test_one_stub_as_context_manager_with_body(self):
        with stub_action('test_service', 'test_action_1',
                         body={'value': 1}) as stub_test_action_1:
            response = self.client.call_action('test_service', 'test_action_1')

        self.assertEqual({'value': 1}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)
        stub_test_action_1.assert_called_once_with({})

    @stub_action('test_service', 'test_action_2')
    @stub_action('test_service', 'test_action_1')
    def test_two_stubs_same_service_as_decorator(self, stub_test_action_1,
                                                 stub_test_action_2):
        stub_test_action_1.return_value = {'value': 1}
        stub_test_action_2.return_value = {'another_value': 2}

        response = self.client.call_action('test_service', 'test_action_1')
        self.assertEqual({'value': 1}, response.body)

        response = self.client.call_action('test_service', 'test_action_2',
                                           {'input_attribute': True})
        self.assertEqual({'another_value': 2}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)
        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'input_attribute': True},
                         stub_test_action_2.call_body)
        stub_test_action_1.assert_called_once_with({})
        stub_test_action_2.assert_called_once_with({'input_attribute': True})

    @stub_action('test_service', 'test_action_2')
    @stub_action('test_service', 'test_action_1')
    def test_two_stubs_same_service_as_decorator_multiple_calls_to_one(
            self, stub_test_action_1, stub_test_action_2):
        stub_test_action_1.return_value = {'value': 1}
        stub_test_action_2.side_effect = ({
            'another_value': 2
        }, {
            'third_value': 3
        })

        response = self.client.call_action('test_service', 'test_action_1')
        self.assertEqual({'value': 1}, response.body)

        response = self.client.call_action('test_service', 'test_action_2',
                                           {'input_attribute': True})
        self.assertEqual({'another_value': 2}, response.body)

        response = self.client.call_action('test_service', 'test_action_2',
                                           {'another_attribute': False})
        self.assertEqual({'third_value': 3}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)
        self.assertEqual(2, stub_test_action_2.call_count)
        self.assertEqual({'another_attribute': False},
                         stub_test_action_2.call_body)
        self.assertEqual(({
            'input_attribute': True
        }, {
            'another_attribute': False
        }), stub_test_action_2.call_bodies)
        stub_test_action_1.assert_called_once_with({})
        stub_test_action_2.assert_has_calls([
            mock.call({'input_attribute': True}),
            mock.call({'another_attribute': False}),
        ])

    @stub_action('test_service', 'test_action_1')
    def test_two_stubs_same_service_split(self, stub_test_action_1):
        stub_test_action_1.return_value = {'value': 1}

        with stub_action('test_service',
                         'test_action_2') as stub_test_action_2:
            stub_test_action_2.return_value = {'another_value': 2}

            response = self.client.call_action('test_service', 'test_action_1')
            self.assertEqual({'value': 1}, response.body)

            response = self.client.call_action('test_service', 'test_action_2',
                                               {'input_attribute': True})
            self.assertEqual({'another_value': 2}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)
        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'input_attribute': True},
                         stub_test_action_2.call_body)

    @stub_action('test_another_service', 'test_action_2')
    @stub_action('test_service', 'test_action_1')
    def test_two_stubs_different_services_as_decorator(self,
                                                       stub_test_action_1,
                                                       stub_test_action_2):
        stub_test_action_1.return_value = {'value': 1}
        stub_test_action_2.return_value = {'another_value': 2}

        response = self.client.call_action('test_service', 'test_action_1')
        self.assertEqual({'value': 1}, response.body)

        response = self.client.call_action('test_another_service',
                                           'test_action_2',
                                           {'input_attribute': True})
        self.assertEqual({'another_value': 2}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)
        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'input_attribute': True},
                         stub_test_action_2.call_body)

    @stub_action('test_service', 'test_action_1')
    def test_two_stubs_different_services_split(self, stub_test_action_1):
        stub_test_action_1.return_value = {'value': 1}

        with stub_action('test_another_service',
                         'test_action_2') as stub_test_action_2:
            stub_test_action_2.return_value = {'another_value': 2}

            response = self.client.call_action('test_service', 'test_action_1')
            self.assertEqual({'value': 1}, response.body)

            response = self.client.call_action('test_another_service',
                                               'test_action_2',
                                               {'input_attribute': True})
            self.assertEqual({'another_value': 2}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)
        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'input_attribute': True},
                         stub_test_action_2.call_body)

    @stub_action('test_service', 'test_action_1', body={'value': 1})
    def test_one_stub_as_decorator_with_real_call_handled(
            self, stub_test_action_1):
        response = self.client.call_action('test_service', 'test_action_1')
        self.assertEqual(response.body, {'value': 1})

        response = self.secondary_stub_client.call_action('cat', 'meow')
        self.assertEqual({'type': 'squeak'}, response.body)

        response = self.secondary_stub_client.call_action('dog', 'bark')
        self.assertEqual({'sound': 'woof'}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)

    def test_one_stub_as_context_manager_with_real_call_handled(self):
        with stub_action('test_service', 'test_action_1',
                         body={'value': 1}) as stub_test_action_1:
            response = self.client.call_action('test_service', 'test_action_1')
            self.assertEqual(response.body, {'value': 1})

            response = self.secondary_stub_client.call_action('cat', 'meow')
            self.assertEqual({'type': 'squeak'}, response.body)

            response = self.secondary_stub_client.call_action('dog', 'bark')
            self.assertEqual({'sound': 'woof'}, response.body)

        self.assertEqual(1, stub_test_action_1.call_count)
        self.assertEqual({}, stub_test_action_1.call_body)

    @stub_action('test_service', 'test_action_2')
    @mock.patch(__name__ + '._test_function', return_value=3)
    def test_as_decorator_with_patch_before(self, mock_randint,
                                            stub_test_action_2):
        stub_test_action_2.return_value = {'value': 99}

        response = self.client.call_actions(
            'test_service',
            [
                ActionRequest(action='test_action_1'),
                ActionRequest(action='test_action_2')
            ],
        )

        self.assertEqual(2, len(response.actions))
        self.assertEqual({'value': 3}, response.actions[0].body)
        self.assertEqual({'value': 99}, response.actions[1].body)

        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({}, stub_test_action_2.call_body)
        mock_randint.assert_called_once_with(0, 99)

    @mock.patch(__name__ + '._test_function', return_value=7)
    @stub_action('test_service', 'test_action_2')
    def test_as_decorator_with_patch_after(self, stub_test_action_2,
                                           mock_randint):
        stub_test_action_2.side_effect = ({'value': 122}, {'also': 157})

        response = self.client.call_actions(
            'test_service',
            [{
                'action': 'test_action_1'
            }, {
                'action': 'test_action_2'
            }, {
                'action': 'test_action_2'
            }],
        )

        self.assertEqual(3, len(response.actions))
        self.assertEqual({'value': 7}, response.actions[0].body)
        self.assertEqual({'value': 122}, response.actions[1].body)
        self.assertEqual({'also': 157}, response.actions[2].body)

        self.assertEqual(2, stub_test_action_2.call_count)
        self.assertEqual(({}, {}), stub_test_action_2.call_bodies)
        stub_test_action_2.assert_has_calls([mock.call({}), mock.call({})])
        mock_randint.assert_called_once_with(0, 99)

    def test_using_start_stop(self):
        action_stubber = stub_action('test_service', 'test_action_1')
        stubbed_action = action_stubber.start()
        stubbed_action.return_value = {'what about': 'this'}

        response = self.client.call_action('test_service', 'test_action_1', {
            'burton': 'guster',
            'sean': 'spencer'
        })
        self.assertEqual({'what about': 'this'}, response.body)

        self.assertEqual(1, stubbed_action.call_count)
        self.assertEqual({
            'burton': 'guster',
            'sean': 'spencer'
        }, stubbed_action.call_body)
        stubbed_action.assert_called_once_with({
            'burton': 'guster',
            'sean': 'spencer'
        })
        action_stubber.stop()

    @stub_action('test_service',
                 'test_action_2',
                 errors=[
                     {
                         'code': 'BAD_FOO',
                         'field': 'foo',
                         'message': 'Nope'
                     },
                 ])
    def test_mock_action_with_error_raises_exception(self, stub_test_action_2):
        with self.assertRaises(Client.CallActionError) as e:
            self.client.call_action('test_service', 'test_action_2',
                                    {'a': 'body'})

        self.assertEqual('BAD_FOO', e.exception.actions[0].errors[0].code)
        self.assertEqual('foo', e.exception.actions[0].errors[0].field)
        self.assertEqual('Nope', e.exception.actions[0].errors[0].message)

        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'a': 'body'}, stub_test_action_2.call_body)
        stub_test_action_2.assert_called_once_with({'a': 'body'})

    @stub_action('test_service', 'test_action_2')
    def test_mock_action_with_error_side_effect_raises_exception(
            self, stub_test_action_2):
        stub_test_action_2.side_effect = ActionError(
            errors=[Error(code='BAR_BAD', field='bar', message='Uh-uh')])

        with self.assertRaises(Client.CallActionError) as e:
            self.client.call_action('test_service', 'test_action_2',
                                    {'a': 'body'})

        self.assertEqual('BAR_BAD', e.exception.actions[0].errors[0].code)
        self.assertEqual('bar', e.exception.actions[0].errors[0].field)
        self.assertEqual('Uh-uh', e.exception.actions[0].errors[0].message)

        self.assertEqual(1, stub_test_action_2.call_count)
        self.assertEqual({'a': 'body'}, stub_test_action_2.call_body)
        stub_test_action_2.assert_called_once_with({'a': 'body'})

    @stub_action('test_service',
                 'test_action_2',
                 errors=[
                     {
                         'code': 'INVALID_BAR',
                         'message': 'A bad message'
                     },
                 ])
    def test_multiple_actions_stop_on_error(self, stub_test_action_2):
        response = self.client.call_actions(
            'test_service',
            [
                ActionRequest(action='test_action_1'),
                ActionRequest(action='test_action_2'),
                ActionRequest(action='test_action_1'),
            ],
            raise_action_errors=False,
        )

        # Called 3 actions, but expected to stop after the error in the second action
        self.assertEqual(2, len(response.actions))
        self.assertEqual('INVALID_BAR', response.actions[1].errors[0].code)
        self.assertEqual('A bad message',
                         response.actions[1].errors[0].message)
        self.assertTrue(stub_test_action_2.called)

    @stub_action('test_service',
                 'test_action_2',
                 errors=[
                     {
                         'code': 'MISSING_BAZ',
                         'field': 'entity_id',
                         'message': 'Your entity ID was missing'
                     },
                 ])
    def test_multiple_actions_continue_on_error(self, mock_test_action_2):
        response = self.client.call_actions(
            'test_service',
            [{
                'action': 'test_action_1'
            }, {
                'action': 'test_action_2'
            }, {
                'action': 'test_action_1'
            }],
            raise_action_errors=False,
            continue_on_error=True,
        )

        # Called 3 actions, and expected all three of them to be called, even with the interrupting error
        self.assertEqual(3, len(response.actions))
        self.assertEqual('MISSING_BAZ', response.actions[1].errors[0].code)
        self.assertEqual('entity_id', response.actions[1].errors[0].field)
        self.assertEqual('Your entity ID was missing',
                         response.actions[1].errors[0].message)
        self.assertTrue(mock_test_action_2.called)
Exemple #6
0
                        'hosts': [
                            (os.environ['REDIS_PORT_6379_TCP_ADDR'],
                             int(os.environ['REDIS_PORT_6379_TCP_PORT'])),
                        ],
                    },
                },
            }
        }
    })

    if sys.argv[1] == 'call_service':
        body = {}
        action = 'call_service'
    elif sys.argv[1] == 'square':
        if len(sys.argv) != 3:
            print('Usage: example_client.sh square [number to square]')
            exit(2)
        body = {'number': int(sys.argv[2])}
        action = 'square'
    else:
        body = {}
        action = 'status'

    print('Calling `example.{}` with request body:'.format(action))
    print(json.dumps(body, indent=4, sort_keys=True))
    print()

    response = client.call_action('example', action, body=body)
    print('Response:')
    print(json.dumps(attr.asdict(response), indent=4, sort_keys=True))
Exemple #7
0
class ServerTestCase(unittest.TestCase):
    """
    Base class for test cases that need to call the server.

    It runs calls to actions through the server stack so they get middleware run
    (for things like request.metrics) and requests/responses run through a
    serializer cycle.
    """

    server_class = None
    server_settings = None

    def setUp(self):
        super(ServerTestCase, self).setUp()

        if self.server_class is None:
            raise RuntimeError(
                'You must specify server_class in ServerTestCase subclasses')

        # Get settings based on Django mode
        if self.server_settings is not None:
            settings = self.server_settings
        else:
            if self.server_class.use_django:
                from django.conf import settings
                settings = settings.SOA_SERVER_SETTINGS
            else:
                settings_module = os.environ.get('PYSOA_SETTINGS_MODULE', None)
                if not settings_module:
                    self.fail(
                        'PYSOA_SETTINGS_MODULE environment variable must be set to run tests.'
                    )
                try:
                    thing = importlib.import_module(settings_module)
                    settings = thing.SOA_SERVER_SETTINGS
                except (ImportError, AttributeError) as e:
                    self.fail(
                        'Could not access {}.SOA_SERVER_SETTINGS: {}'.format(
                            settings_module, e))

        self.client = Client(
            {
                self.server_class.service_name: {
                    'transport': {
                        'path':
                        'pysoa.common.transport.local:LocalClientTransport',
                        'kwargs': {
                            'server_class': self.server_class,
                            'server_settings': settings,
                        },
                    },
                },
            }, )

    def call_action(self, action, body=None, service_name=None, **kwargs):
        # Using this enables tests that call the same action dozens of times to not have to code in the service name
        # for every single action call (but they still can by passing in `service_name`)
        return self.client.call_action(service_name
                                       or self.server_class.service_name,
                                       action,
                                       body=body,
                                       **kwargs)

    def assertActionRunsWithAndReturnErrors(self, action, body, **kwargs):
        with raises_call_action_error() as exc_info:
            self.call_action(action, body, **kwargs)
        return exc_info.soa_errors

    def assertActionRunsWithFieldErrors(self,
                                        action,
                                        body,
                                        field_errors,
                                        only=False,
                                        **kwargs):
        with raises_field_errors(field_errors, only=only):
            self.call_action(action, body, **kwargs)

    def assertActionRunsWithOnlyFieldErrors(self, action, body, field_errors,
                                            **kwargs):
        self.assertActionRunsWithFieldErrors(action,
                                             body,
                                             field_errors,
                                             only=True,
                                             **kwargs)

    def assertActionRunsWithErrorCodes(self,
                                       action,
                                       body,
                                       error_codes,
                                       only=False,
                                       **kwargs):
        with raises_error_codes(error_codes, only=only):
            self.call_action(action, body, **kwargs)

    def assertActionRunsWithOnlyErrorCodes(self, action, body, error_codes,
                                           **kwargs):
        self.assertActionRunsWithErrorCodes(action,
                                            body,
                                            error_codes,
                                            only=True,
                                            **kwargs)