예제 #1
0
    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'})
예제 #2
0
    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'})
예제 #3
0
    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'})
예제 #4
0
    def test_check_client_settings_with_settings(self):
        client = Client({
            'foo': {'transport': {'path': 'pysoa.test.stub_service:StubClientTransport'}},
            'bar': {'transport': {'path': 'pysoa.test.stub_service:StubClientTransport'}},
            'baz': {'transport': {'path': 'pysoa.test.stub_service:StubClientTransport'}},
            'qux': {'transport': {'path': 'pysoa.test.stub_service:StubClientTransport'}},
        })

        action_request = EnrichedActionRequest(action='status', body={}, switches=None, client=client)

        baz_body = {
            'conformity': '1.2.3',
            'pysoa': '1.0.2',
            'python': '3.7.4',
            'version': '9.7.8',
        }

        with stub_action('foo', 'status') as foo_stub,\
                stub_action('bar', 'status', errors=[Error('BAR_ERROR', 'Bar error')]),\
                stub_action('baz', 'status', body=baz_body),\
                stub_action('qux', 'status') as qux_stub:
            foo_stub.return_value = JobResponse(errors=[Error('FOO_ERROR', 'Foo error')])
            qux_stub.side_effect = MessageReceiveTimeout('Timeout calling qux')

            response = _CheckOtherServicesAction()(action_request)

        self.assertIsInstance(response, ActionResponse)
        self.assertEqual(six.text_type(conformity.__version__), response.body['conformity'])
        self.assertEqual(six.text_type(pysoa.__version__), response.body['pysoa'])
        self.assertEqual(six.text_type(platform.python_version()), response.body['python'])
        self.assertEqual('8.71.2', response.body['version'])
        self.assertIn('healthcheck', response.body)
        self.assertEqual([], response.body['healthcheck']['warnings'])
        self.assertIn(
            ('FOO_CALL_ERROR', six.text_type([Error('FOO_ERROR', 'Foo error')])),
            response.body['healthcheck']['errors'],
        )
        self.assertIn(
            ('BAR_STATUS_ERROR', six.text_type([Error('BAR_ERROR', 'Bar error')])),
            response.body['healthcheck']['errors'],
        )
        self.assertIn(
            ('QUX_TRANSPORT_ERROR', 'Timeout calling qux'),
            response.body['healthcheck']['errors'],
        )
        self.assertEqual(3, len(response.body['healthcheck']['errors']))
        self.assertEqual(
            {
                'services': {
                    'baz': {
                        'conformity': '1.2.3',
                        'pysoa': '1.0.2',
                        'python': '3.7.4',
                        'version': '9.7.8',
                    },
                },
            },
            response.body['healthcheck']['diagnostics'],
        )
예제 #5
0
 def test_assert_not_wanted_errors_mismatch_field(self):
     assertions.assert_actual_list_not_subset(
         [
             Error(code='FOO',
                   message=AnyValue('str'),
                   field=AnyValue('str', permit_none=True)),
             Error(code='BAR', message=AnyValue('str'), field='bar_field'),
         ],
         [
             Error(code='BAR', message='Bar message', field=None),
         ],
     )
예제 #6
0
 def test_assert_any_wanted_error_match_with_field(self):
     assertions.assert_expected_list_subset_of_actual(
         [
             Error(code='FOO',
                   message=AnyValue('str'),
                   field=AnyValue('str', permit_none=True)),
         ],
         [
             Error(code='FOO', message='Foo message', field='foo_field'),
             Error(code='BAR', message='Bar message', field=None),
         ],
     )
예제 #7
0
 def test_assert_all_wanted_errors_mismatch_empty_list_other_way(self):
     with self.assertRaises(AssertionError):
         assertions.assert_lists_match_any_order(
             [],
             [
                 Error(code='FOO',
                       message=AnyValue('str'),
                       field=AnyValue('str', permit_none=True)),
                 Error(code='BAR',
                       message=AnyValue('str'),
                       field=AnyValue('str', permit_none=True)),
             ],
         )
예제 #8
0
 def test_assert_any_wanted_error_mismatch_empty_actual_list(self):
     with self.assertRaises(AssertionError):
         assertions.assert_expected_list_subset_of_actual(
             [
                 Error(code='FOO',
                       message=AnyValue('str'),
                       field=AnyValue('str', permit_none=True)),
                 Error(code='BAR',
                       message=AnyValue('str'),
                       field=AnyValue('str', permit_none=True)),
             ],
             [],
         )
예제 #9
0
 def test_assert_all_wanted_errors_match_different_order(self):
     assertions.assert_lists_match_any_order(
         [
             Error(code='FOO',
                   message=AnyValue('str'),
                   field=AnyValue('str', permit_none=True)),
             Error(code='BAR',
                   message=AnyValue('str'),
                   field=AnyValue('str', permit_none=True)),
         ],
         [
             Error(code='BAR', message='Bar message', field=None),
             Error(code='FOO', message='Foo message', field='foo_field'),
         ],
     )
예제 #10
0
    def test_call_actions_parallel_with_extras(self):
        """
        Test that call_actions_parallel works to call multiple actions run parallel on a single service using extra
        kwargs to more finely control behavior.
        """
        action_responses = self.client.call_actions_parallel(
            'service_2',
            [
                ActionRequest(action='action_3'),
                ActionRequest(action='action_with_errors'),
                ActionRequest(action='action_4'),
            ],
            timeout=2,
            raise_action_errors=False,
            continue_on_error=True,
        )

        self.assertIsNotNone(action_responses)

        action_responses = list(action_responses)
        self.assertEqual(3, len(action_responses))
        self.assertEqual({'cat': 'dog'}, action_responses[0].body)
        self.assertEqual({}, action_responses[1].body)
        self.assertEqual(
            [Error(code=ERROR_CODE_INVALID, message='Invalid input', field='foo')],
            action_responses[1].errors,
        )
        self.assertEqual({'selected': True, 'count': 7}, action_responses[2].body)
예제 #11
0
def test_raises_error_codes_missing(codes):
    errors = [Error(code=code, message='bam') for code in codes]
    with pytest.raises(pytest.raises.Exception):
        with raises_error_codes(['AUTH_MISSING']):
            raise Client.CallActionError(
                actions=[ActionResponse(action='', errors=errors)]
            )
예제 #12
0
    def test_call_jobs_parallel_job_errors_not_raised(self):
        """
        Test that call_jobs_parallel returns job errors instead of raising them when asked.
        """
        job_responses = self.client.call_jobs_parallel(
            [
                {'service_name': 'service_1', 'actions': [{'action': 'action_1'}, {'action': 'action_2'}]},
                {'service_name': 'error_service', 'actions': [{'action': 'job_error'}]},
                {'service_name': 'service_2', 'actions': [{'action': 'action_3'}]},
                {'service_name': 'service_2', 'actions': [{'action': 'action_4'}]},
            ],
            raise_job_errors=False,
        )

        self.assertIsNotNone(job_responses)

        self.assertEqual(4, len(job_responses))
        self.assertEqual(2, len(job_responses[0].actions))
        self.assertEqual({'foo': 'bar'}, job_responses[0].actions[0].body)
        self.assertEqual({'baz': 3}, job_responses[0].actions[1].body)
        self.assertEqual(0, len(job_responses[1].actions))
        self.assertEqual([Error(code='BAD_JOB', message='You are a bad job')], job_responses[1].errors)
        self.assertEqual(1, len(job_responses[2].actions))
        self.assertEqual({'cat': 'dog'}, job_responses[2].actions[0].body)
        self.assertEqual(1, len(job_responses[3].actions))
        self.assertEqual({'selected': True, 'count': 7}, job_responses[3].actions[0].body)
예제 #13
0
    def test_call_actions_raises_exception_on_action_error(self):
        """Client.call_actions raises CallActionError when any action response is an error."""
        action_request = [
            {
                'action': 'action_1',
                'body': {'foo': 'bar'},
            },
            {
                'action': 'action_2',
                'body': {},
            },
        ]
        error_expected = [
            Error(
                code=ERROR_CODE_INVALID,
                message='Invalid input',
                field='foo',
            )
        ]
        self.client_settings[SERVICE_NAME]['transport']['kwargs']['action_map']['action_1'] = {'errors': error_expected}
        client = Client(self.client_settings)

        for actions in (action_request, [ActionRequest(**a) for a in action_request]):
            with self.assertRaises(Client.CallActionError) as e:
                client.call_actions(SERVICE_NAME, actions)
                self.assertEqual(len(e.value.actions), 1)
                self.assertEqual(e.value.actions[0].action, 'action_1')
                error_response = e.value.actions[0].errors
                self.assertEqual(len(error_response), 1)
                self.assertEqual(error_response[0].code, error_expected[0]['code'])
                self.assertEqual(error_response[0].message, error_expected[0]['message'])
                self.assertEqual(error_response[0].field, error_expected[0]['field'])
예제 #14
0
 def test_call_actions_no_raise_action_errors(self):
     action_request = [
         {
             'action': 'action_1',
             'body': {'foo': 'bar'},
         },
         {
             'action': 'action_2',
             'body': {},
         },
     ]
     error_expected = [
         Error(
             code=ERROR_CODE_INVALID,
             message='Invalid input',
             field='foo'
         )
     ]
     self.client_settings[SERVICE_NAME]['transport']['kwargs']['action_map']['action_2'] = {'errors': error_expected}
     client = Client(self.client_settings)
     for actions in (action_request, [ActionRequest(**a) for a in action_request]):
         response = client.call_actions(SERVICE_NAME, actions, raise_action_errors=False)
         self.assertEqual(response.actions[0].body, {'foo': 'bar'})
         self.assertEqual(response.actions[1].errors, error_expected)
         self.assertIsNotNone(response.context['correlation_id'])
예제 #15
0
    def test_action_one_switch_twelve_with_errors(self):
        settings = {'foo': 'bar'}

        action = SwitchedActionOne(settings)

        with self.assertRaises(ActionError) as error_context:
            action(
                EnrichedActionRequest(action='one',
                                      body={'animal': 'cat'},
                                      switches=[12]))

        self.assertEqual(2, len(error_context.exception.errors))
        self.assertIn(Error('MISSING', 'Missing key: planet', field='planet'),
                      error_context.exception.errors)
        self.assertIn(Error('UNKNOWN', 'Extra keys present: animal'),
                      error_context.exception.errors)
예제 #16
0
 def test_assert_not_wanted_errors_match_list_with_field(self):
     with self.assertRaises(AssertionError):
         assertions.assert_actual_list_not_subset(
             [
                 Error(code='FOO',
                       message=AnyValue('str'),
                       field=AnyValue('str', permit_none=True)),
                 Error(code='BAR',
                       message=AnyValue('str'),
                       field=AnyValue('str', permit_none=True)),
             ],
             [
                 Error(code='FOO', message='Foo message',
                       field='foo_field'),
             ],
         )
예제 #17
0
def test_raises_field_errors_match_multiple(codes):
    errors = [Error(code=code, field=field, message='bam') for field, code in codes]
    with raises_field_errors({'event_id': 'UNKNOWN', 'organization_id': 'INVALID'}) as exc_info:
        raise Client.CallActionError(
            actions=[ActionResponse(action='', errors=errors)]
        )

    assert exc_info.soa_errors == errors
예제 #18
0
    def test_call_actions_parallel_job_errors_raised(self):
        """
        Test that call_actions_parallel raises job errors when they occur
        """
        with self.assertRaises(self.client.JobError) as error_context:
            self.client.call_actions_parallel('error_service', [{'action': 'job_error'}])

        self.assertEqual([Error(code='BAD_JOB', message='You are a bad job')], error_context.exception.errors)
예제 #19
0
def test_raises_error_codes_multiple(codes):
    errors = [Error(code=code, message='bam') for code in codes]
    with raises_error_codes(['TEST', 'AUTH_MISSING']) as exc_info:
        raise Client.CallActionError(
            actions=[ActionResponse(action='', errors=errors)]
        )

    assert exc_info.soa_errors == errors
예제 #20
0
 def run(self, request):
     try:
         # noinspection PyUnresolvedReferences
         return {
             'random': random.randint(request.body['min'], request.body['max']),
             'response': function_which_shall_be_mocked(
                 request.body['max'],
                 request.body['min'],
                 **request.body['kwargs']
             ),
             'extra': function_which_shall_be_mocked.extra.value().for_me,
         }
     except AttributeError:
         raise ActionError(errors=[Error('ATTRIBUTE_ERROR', 'An attribute error was raised')])
     except BytesWarning:
         raise ActionError(errors=[Error('BYTES_WARNING', 'A bytes warning was raised')])
     except ExpectedException:
         raise ActionError(errors=[Error('EXPECTED_EXCEPTION', 'An expected exception was raised')])
예제 #21
0
def test_raises_field_errors_missing(code, field):
    errors = [
        Error(code=code, message='test fail', field=field),
    ]
    with pytest.raises(pytest.raises.Exception):
        with raises_field_errors({'event_id': 'UNKNOWN'}):
            raise Client.CallActionError(
                actions=[ActionResponse(action='', errors=errors)]
            )
예제 #22
0
 def test_assert_not_wanted_errors_array_empty(self):
     assertions.assert_actual_list_not_subset(
         [
             Error(code='INVALID',
                   message=AnyValue('str'),
                   field=AnyValue('str', permit_none=True))
         ],
         [],
     )
예제 #23
0
    def execute_job(self, job_request):
        """
        Processes and runs the ActionRequests on the Job.
        """
        # Run the Job's Actions
        job_response = JobResponse()
        job_switches = RequestSwitchSet(job_request['context']['switches'])
        for i, raw_action_request in enumerate(job_request['actions']):
            action_request = EnrichedActionRequest(
                action=raw_action_request['action'],
                body=raw_action_request.get('body', None),
                switches=job_switches,
                context=job_request['context'],
                control=job_request['control'],
                client=job_request['client'],
            )
            if action_request.action in self.action_class_map:
                # Get action to run
                action = self.action_class_map[action_request.action](
                    self.settings)
                # Wrap it in middleware
                wrapper = self.make_middleware_stack(
                    [m.action for m in self.middleware],
                    action,
                )
                # Execute the middleware stack
                try:
                    action_response = wrapper(action_request)
                except ActionError as e:
                    # Error: an error was thrown while running the Action (or Action middleware)
                    action_response = ActionResponse(
                        action=action_request.action,
                        errors=e.errors,
                    )
            else:
                # Error: Action not found.
                action_response = ActionResponse(
                    action=action_request.action,
                    errors=[
                        Error(
                            code=ERROR_CODE_UNKNOWN,
                            message=
                            'The action "{}" was not found on this server.'.
                            format(action_request.action),
                            field='action',
                        )
                    ],
                )

            job_response.actions.append(action_response)
            if (action_response.errors and not job_request['control'].get(
                    'continue_on_error', False)):
                # Quit running Actions if an error occurred and continue_on_error is False
                break

        return job_response
예제 #24
0
class ErrorServer(Server):
    service_name = 'error_service'

    # noinspection PyTypeChecker
    action_class_map = {
        'job_error': lambda *_, **__: (_ for _ in ()).throw(
            JobError(errors=[Error(code='BAD_JOB', message='You are a bad job')])
        ),
        'okay_action': lambda *_, **__: lambda *_, **__: ActionResponse(action='okay_action', body={'no_error': True}),
    }
예제 #25
0
    def process_job(self, job_request):
        """
        Validate, execute, and run the job request, wrapping it with any applicable job middleware.

        :param job_request: The job request
        :type job_request: dict

        :return: A `JobResponse` object
        :rtype: JobResponse

        :raise: JobError
        """

        try:
            # Validate JobRequest message
            validation_errors = [
                Error(
                    code=error.code,
                    message=error.message,
                    field=error.pointer,
                ) for error in (JobRequestSchema.errors(job_request) or [])
            ]
            if validation_errors:
                raise JobError(errors=validation_errors)

            # Add the client object in case a middleware wishes to use it
            job_request['client'] = self.make_client(job_request['context'])

            # Add the async event loop in case a middleware wishes to use it
            job_request['async_event_loop'] = self._async_event_loop
            if self._async_event_loop_thread:
                job_request[
                    'run_coroutine'] = self._async_event_loop_thread.run_coroutine
            else:
                job_request['run_coroutine'] = None

            # Build set of middleware + job handler, then run job
            wrapper = self.make_middleware_stack(
                [m.job for m in self.middleware],
                self.execute_job,
            )
            job_response = wrapper(job_request)
            if 'correlation_id' in job_request['context']:
                job_response.context['correlation_id'] = job_request[
                    'context']['correlation_id']
        except JobError as e:
            self.metrics.counter('server.error.job_error').increment()
            job_response = JobResponse(errors=e.errors, )
        except Exception as e:
            # Send an error response if no middleware caught this.
            # Formatting the error might itself error, so try to catch that
            self.metrics.counter('server.error.unhandled_error').increment()
            return self.handle_job_exception(e)

        return job_response
예제 #26
0
    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'})
예제 #27
0
    def handle_job_error_code(self, code, message, request_for_logging, response_for_logging, extra=None):
        log_extra = {'data': {'request': request_for_logging, 'response': response_for_logging}}
        if extra:
            log_extra['data'].update(extra)

        self.logger.error(
            message,
            exc_info=True,
            extra=log_extra,
        )
        return JobResponse(errors=[Error(code=code, message=message)])
예제 #28
0
 def run(self, request):
     if self.errors:
         raise ActionError(errors=[
             Error(
                 code=e['code'],
                 message=e['message'],
                 field=e.get('field'),
             ) if not isinstance(e, Error) else e for e in self.errors
         ])
     else:
         return self.body
예제 #29
0
    def validate(self, request):

        super().validate(request)
        try:
            Category.objects.get(name=request.get('category'))
        except Category.DoesNotExist:
            raise ActionError([
                Error(
                    message='category not found',
                    field='category',
                ),
            ], )
예제 #30
0
 def test_call_actions_raises_exception_on_job_error(self):
     """Client.call_actions raises Client.JobError when a JobError occurs on the server."""
     client = Client(self.client_settings)
     errors = [Error(code=ERROR_CODE_SERVER_ERROR, message='Something went wrong!')]
     with mock.patch.object(
         client._get_handler(SERVICE_NAME).transport.server,
         'execute_job',
         new=mock.Mock(side_effect=JobError(errors)),
     ):
         with self.assertRaises(Client.JobError) as e:
             client.call_action(SERVICE_NAME, 'action_1')
             self.assertEqual(e.errors, errors)