コード例 #1
0
    def test_action_one_switch_twelve_with_errors(self):
        settings = {'foo': 'bar'}

        action = SwitchedActionOne(cast(ServerSettings, 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',
                  is_caller_error=True),
            error_context.exception.errors,
        )
        self.assertIn(
            Error('UNKNOWN',
                  'Extra keys present: animal',
                  is_caller_error=True),
            error_context.exception.errors,
        )
コード例 #2
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,  # type: ignore
         }
     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')
         ])
コード例 #3
0
    def run(self, request):
        if request.body.get('errors') == 1:
            raise ActionError([Error('FOO', 'Foo error')])
        if request.body.get('errors') == 2:
            raise ActionError(
                [Error('BAZ', 'Baz error'),
                 Error('QUX', 'Qux error')])

        return {'salutation': 'Hello, {}'.format(request.body['name'])}
コード例 #4
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)),  # type: ignore
         ],
         [
             Error(code='FOO', message='Foo message', field='foo_field'),
             Error(code='BAR', message='Bar message', field=None),
         ],
     )
コード例 #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)),  # type: ignore
             Error(code='BAR', message=AnyValue('str'),
                   field='bar_field'),  # type: ignore
         ],
         [
             Error(code='BAR', message='Bar message', field=None),
         ],
     )
コード例 #6
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)),  # type: ignore
                 Error(code='BAR',
                       message=AnyValue('str'),
                       field=AnyValue('str',
                                      permit_none=True)),  # type: ignore
             ],
             [],
         )
コード例 #7
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)),  # type: ignore
             Error(code='BAR',
                   message=AnyValue('str'),
                   field=AnyValue('str', permit_none=True)),  # type: ignore
         ],
         [
             Error(code='BAR', message='Bar message', field=None),
             Error(code='FOO', message='Foo message', field='foo_field'),
         ],
     )
コード例 #8
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)),  # type: ignore
                 Error(code='BAR',
                       message=AnyValue('str'),
                       field=AnyValue('str',
                                      permit_none=True)),  # type: ignore
             ],
         )
コード例 #9
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
コード例 #10
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)])
コード例 #11
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)),  # type: ignore
                 Error(code='BAR',
                       message=AnyValue('str'),
                       field=AnyValue('str',
                                      permit_none=True)),  # type: ignore
             ],
             [
                 Error(code='FOO', message='Foo message',
                       field='foo_field'),
             ],
         )
コード例 #12
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))
         ],  # type: ignore
         [],
     )
コード例 #13
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
コード例 #14
0
    def test_call_local_action_other_request_details(self):
        action = mock.MagicMock()
        action.return_value.return_value = ActionResponse(
            action='another_foo', errors=[Error('BAR', 'Bar error')])

        server = mock.MagicMock()
        server.settings = {'a_setting': 'a_value'}
        server.action_class_map = {'another_foo': action}

        r = EnrichedActionRequest(action='foo',
                                  body={'bar': 'baz'},
                                  context={'auth': 'abc123'},
                                  control={'speed': 5})
        r._server = server

        with pytest.raises(ActionError) as error_context:
            r.call_local_action('another_foo', {'height': '10m'})

        assert error_context.value.errors[0].code == 'BAR'

        action.assert_called_once_with(server.settings)
        assert action.return_value.call_count == 1

        other_r = action.return_value.call_args[0][0]
        assert other_r is not r
        assert other_r != r
        assert other_r.action == 'another_foo'
        assert other_r.body == {'height': '10m'}
        assert other_r.context == {'auth': 'abc123'}
        assert other_r.control == {'speed': 5}
        assert getattr(other_r, '_server') is server

        action.reset_mock()

        response = r.call_local_action('another_foo', {'height': '10m'},
                                       raise_action_errors=False)
        assert response.action == 'another_foo'
        assert response.errors
        assert response.errors[0].code == 'BAR'

        action.assert_called_once_with(server.settings)
        assert action.return_value.call_count == 1

        other_r = action.return_value.call_args[0][0]
        assert other_r is not r
        assert other_r != r
        assert other_r.action == 'another_foo'
        assert other_r.body == {'height': '10m'}
        assert other_r.context == {'auth': 'abc123'}
        assert other_r.control == {'speed': 5}
        assert getattr(other_r, '_server') is server
コード例 #15
0
    def test_call_local_action_standard_request(self):
        action = mock.MagicMock()
        action.return_value.side_effect = ActionError(
            [Error('FOO', 'Foo error')])

        server = mock.MagicMock()
        server.settings = {'a_setting': 'a_value'}
        server.action_class_map = {'other_foo': action}

        r = EnrichedActionRequest(action='foo', body={'bar': 'baz'})
        r._server = server

        with pytest.raises(ActionError) as error_context:
            r.call_local_action('other_foo', {'color': 'red'})

        assert error_context.value.errors[0].code == 'FOO'

        action.assert_called_once_with(server.settings)
        assert action.return_value.call_count == 1

        other_r = action.return_value.call_args[0][0]
        assert other_r is not r
        assert other_r != r
        assert other_r.action == 'other_foo'
        assert other_r.body == {'color': 'red'}
        assert other_r.context == {}
        assert other_r.control == {}
        assert getattr(other_r, '_server') is server

        action.reset_mock()

        response = r.call_local_action('other_foo', {'color': 'red'},
                                       raise_action_errors=False)
        assert response.action == 'other_foo'
        assert response.errors
        assert response.errors[0].code == 'FOO'

        action.assert_called_once_with(server.settings)
        assert action.return_value.call_count == 1

        other_r = action.return_value.call_args[0][0]
        assert other_r is not r
        assert other_r != r
        assert other_r.action == 'other_foo'
        assert other_r.body == {'color': 'red'}
        assert other_r.context == {}
        assert other_r.control == {}
        assert getattr(other_r, '_server') is server
コード例 #16
0
class ProcessJobServer(Server):
    """
    Stub server to test against.
    """
    service_name = 'test_service'
    action_class_map = {
        'respond_actionerror':
        factories.ActionFactory(exception=ActionError(errors=[
            Error(
                code=ERROR_CODE_INVALID,
                message='This field is invalid.',
                field='body.field',
            )
        ])),
        'respond_empty':
        factories.ActionFactory(),
    }
コード例 #17
0
    def __call__(self, action_request):  # type: (EnrichedActionRequest) -> ActionResponse
        """
        Main entry point for actions from the `Server` (or potentially from tests). Validates that the request matches
        the `request_schema`, then calls `validate()`, then calls `run()` if `validate()` raised no errors, and then
        validates that the return value from `run()` matches the `response_schema` before returning it in an
        `ActionResponse`.

        :param action_request: The request object

        :return: The response object

        :raise: ActionError, ResponseValidationError
        """
        # Validate the request
        if self.request_schema:
            errors = [
                Error(
                    code=error.code,
                    message=error.message,
                    field=error.pointer,
                    is_caller_error=True,
                )
                for error in (self.request_schema.errors(action_request.body) or [])
            ]
            if errors:
                raise ActionError(errors=errors, set_is_caller_error_to=None)
        # Run any custom validation
        self.validate(action_request)
        # Run the body of the action
        response_body = self.run(action_request)
        # Validate the response body. Errors in a response are the problem of
        # the service, and so we just raise a Python exception and let error
        # middleware catch it. The server will return a SERVER_ERROR response.
        if self.response_schema:
            conformity_errors = self.response_schema.errors(response_body)
            if conformity_errors:
                raise ResponseValidationError(action=action_request.action, errors=conformity_errors)
        # Make an ActionResponse and return it
        if response_body is not None:
            return ActionResponse(
                action=action_request.action,
                body=response_body,
            )
        else:
            return ActionResponse(action=action_request.action)
コード例 #18
0
def _start_stubbed_actions(test_target):
    if 'stubbed_actions' in test_target:
        # We must start the stubs in a predictable order...
        for service, action in sorted(
                six.iterkeys(test_target['stubbed_actions'])):
            stub_config = test_target['stubbed_actions'][service, action]
            if 'errors' in stub_config:
                stub = stub_action(
                    service,
                    action,
                    errors=[Error(**e) for e in stub_config['errors']])
            else:
                stub = stub_action(service,
                                   action,
                                   body=stub_config.get('body', {}))

            mock_action = stub.start()
            stub_config['started_stub'] = stub
            stub_config['mock_action'] = mock_action
コード例 #19
0
def _replace_errors_if_necessary(errors, is_caller_error):
    # type: (Iterable[Error], bool) -> List[Error]
    new_errors = []
    for e in errors:
        if e.is_caller_error == is_caller_error:
            new_errors.append(e)
        else:
            # Error is immutable, so return a new one
            new_errors.append(
                Error(
                    code=e.code,
                    message=e.message,
                    field=e.field,
                    traceback=e.traceback,
                    variables=e.variables,
                    denied_permissions=e.denied_permissions,
                    is_caller_error=is_caller_error,
                ))
    return new_errors
コード例 #20
0
    def ingest_from_parsed_test_fixture(self, action_case, test_case, parse_results, file_name, line_number):
        path = 'expects_{not_q}{job_q}{exact_q}error'.format(
            not_q='not_' if getattr(parse_results, 'not', None) else '',
            job_q='job_' if parse_results.job else '',
            exact_q='exact_' if parse_results.exact else '',
        )

        try:
            errors = path_get(action_case, path)
        except (KeyError, IndexError):
            errors = []
            path_put(action_case, path, errors)

        # noinspection PyTypeChecker
        errors.append(Error(  # type: ignore
            code=parse_results.error_code,
            message=getattr(parse_results, 'error_message', None) or AnyValue('str'),  # type: ignore
            field=getattr(parse_results, 'field_name', None) or AnyValue('str', permit_none=True),  # type: ignore
            traceback=AnyValue('str', permit_none=True),  # type: ignore
            variables=AnyValue('dict', permit_none=True),  # type: ignore
            denied_permissions=AnyValue('list', permit_none=True),  # type: ignore
            is_caller_error=AnyValue('bool'),  # type: ignore
        ))
コード例 #21
0
def invalid_organization_id_field_error():
    return Error(code='INVALID',
                 field='organization_id',
                 message='bad organization_id')
コード例 #22
0
def auth_missing_error():
    return Error(code='AUTH_MISSING', message='where did ur auth go')
コード例 #23
0
def invalid_event_id_field_error():
    return Error(code='INVALID', field='event_id', message='bad event_id')
コード例 #24
0
 def run(self, request):
     if self.errors:
         raise ActionError(errors=[
             e if isinstance(e, Error) else Error(**e) for e in self.errors
         ])
     return self.body
コード例 #25
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={},
                                               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'],
        )
コード例 #26
0
ファイル: types.py プロジェクト: WeilerWebServices/Eventbrite
def _convert_errors(errors):
    # type: (Union[Iterable[Mapping[six.text_type, Any]], Iterable[Error]]) -> List[Error]
    value = []  # type: List[Error]
    for a in errors:
        value.append(a if isinstance(a, Error) else Error(**a))
    return value
コード例 #27
0
 def _process_stub_action_stub_job_error(body):
     raise JobError(errors=[
         Error(code='CAT_ERROR', message='Your cat broke the vase'),
         Error(code='DOG_ERROR', message='Your dog ate the couch'),
     ])
コード例 #28
0
def unknown_event_id_field_error():
    return Error(code='UNKNOWN', field='event_id', message='bad event_id')
コード例 #29
0
    def call_local_action(self,
                          action,
                          body,
                          raise_action_errors=True,
                          is_caller_error=False,
                          context=None):
        # type: (six.text_type, Body, bool, bool, Optional[Context]) -> ActionResponse
        """
        This helper calls another action, locally, that resides on the same service, using the provided action name
        and body. The called action will receive a copy of this request object with different action and body details.

        The use of this helper differs significantly from using the PySOA client to call an action. Notably:

        * The configured transport is not involved, so no socket activity or serialization/deserialization takes place.
        * PySOA server metrics are not recorded and post-action cleanup activities do not occur.
        * No "job request" is ever created or transacted.
        * No middleware is executed around this action (though, in the future, we might change this decision and add
          middleware execution to this helper).

        :param action: The action to call (must exist within the `action_class_map` from the `Server` class)
        :param body: The body to send to the action
        :param raise_action_errors: If `True` (the default), all action errors will be raised; otherwise, an
                                    `ActionResponse` containing the errors will be returned.
        :param is_caller_error: If `True` (defaults to `False`), raised action errors will be marked as the
                                responsibility of the caller. Action errors are usually the responsibility of the
                                caller, but the default here is the opposite since the responsibility usually lies in
                                the service that is calling itself and should know better.
        :param context: If specified, any values in this dictionary will override conflicting values in the cloned
                        context.

        :return: the action response.

        :raises: ActionError
        """
        server = getattr(self, '_server', None)
        if not server:
            # This is never a caller error, because it can only happen due to a bug in PySOA or the service.
            errors = [
                Error(
                    code=ERROR_CODE_SERVER_ERROR,
                    message=
                    "No `_server` attribute set on action request object (and can't make request without it)",
                    is_caller_error=False,
                )
            ]
            if raise_action_errors:
                raise ActionError(errors, set_is_caller_error_to=None)
            return ActionResponse(action=action, errors=errors)

        if action not in server.action_class_map:
            # This is never a caller error, because it can only happen due to a bug in the service calling itself.
            errors = [
                Error(
                    code=ERROR_CODE_UNKNOWN,
                    message='The action "{}" was not found on this server.'.
                    format(action),
                    field='action',
                    is_caller_error=False,
                )
            ]
            if raise_action_errors:
                raise ActionError(errors, set_is_caller_error_to=None)
            return ActionResponse(action=action, errors=errors)

        action_type = server.action_class_map[action]  # type: ActionType
        action_callable = action_type(server.settings)

        new_context = copy.copy(self.context)
        if context:
            new_context.update(context)

        request = self.__class__(
            action=action,
            body=body,
            context=new_context,
            # Dynamically copy all Attrs attributes so that subclasses introducing other Attrs can still work properly
            **{
                a.name: getattr(self, a.name)
                for a in getattr(self, '__attrs_attrs__')
                if a.name not in ('action', 'body', 'context')
            })
        request._server = server

        try:
            response = action_callable(request)
        except ActionError as e:
            if raise_action_errors:
                raise
            return ActionResponse(action=action, errors=e.errors)

        if raise_action_errors and response.errors:
            raise ActionError(response.errors,
                              set_is_caller_error_to=is_caller_error)

        return response
コード例 #30
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)])