def test_call_jobs_parallel_future_success(self, mock_present_sounds, mock_past_sounds): client = Client({}) future = client.call_jobs_parallel_future( [ {'service_name': 'future_service', 'actions': [ {'action': 'present_sounds', 'body': {'where': 'here'}}, ]}, {'service_name': 'future_service', 'actions': [ {'action': 'past_sounds', 'body': {'where': 'there'}}, ]}, ], ) mock_present_sounds.assert_called_once_with({'where': 'here'}) mock_past_sounds.assert_called_once_with({'where': 'there'}) assert len(future.result()) == 2 assert future.result()[0].actions[0].errors == [] assert future.result()[0].actions[0].action == 'present_sounds' assert future.result()[0].actions[0].body == {'when': 'present'} assert future.result()[1].actions[0].errors == [] assert future.result()[1].actions[0].action == 'past_sounds' assert future.result()[1].actions[0].body == {'when': 'past'}
def test_client_settings(self): """The client is successfully instantiated with settings, Redis Transport""" settings_dict = { 'test_service': { 'transport': { 'path': 'pysoa.common.transport.redis_gateway.client:RedisClientTransport', 'kwargs': { 'backend_type': REDIS_BACKEND_TYPE_STANDARD, 'serializer_config': { 'path': 'pysoa.common.serializer:JSONSerializer' } } }, }, } client = Client(settings_dict) handler = client._get_handler('test_service') assert isinstance(handler.transport, RedisClientTransport) assert handler.transport._send_queue_name == 'service.test_service' assert isinstance(handler.transport.core, RedisTransportCore) assert handler.transport.core.backend_type == REDIS_BACKEND_TYPE_STANDARD assert isinstance(handler.transport.core.serializer, JSONSerializer)
def test_send_request_with_suppress_response_then_get_response_error(self): """ Client.send_request with suppress_response sends a valid request and Client.get_all_responses returns no response because the response was suppressed """ action_request = [ { 'action': 'action_1', 'body': {}, }, { 'action': 'action_2', 'body': {}, }, ] client = Client(self.client_settings) responses = list(client.get_all_responses(SERVICE_NAME)) self.assertEqual(len(responses), 0) request_id = client.send_request( SERVICE_NAME, action_request, switches={1}, suppress_response=True, ) self.assertTrue(request_id >= 0) responses = list(client.get_all_responses(SERVICE_NAME)) self.assertEqual(len(responses), 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'])
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'])
def test_call_action(self): """Client.call_action sends a valid request and returns a valid response without errors.""" client = Client(self.client_settings) response = client.call_action(SERVICE_NAME, 'action_1') self.assertTrue(isinstance(response, ActionResponse)) self.assertEqual(response.action, 'action_1') self.assertEqual(response.body['foo'], 'bar')
def test_call_action_future_verify_traceback(self, mock_present_sounds): client = Client({}) future = client.call_action_future('future_service', 'present_sounds', body={'hello': 'world'}) try: assert future.result() assert False, 'We should not have hit this line of code' except client.CallActionError: _, __, tb1 = sys.exc_info() try: assert future.result() assert False, 'We should not have hit this line of code' except client.CallActionError: _, __, tb2 = sys.exc_info() try: assert future.result() assert False, 'We should not have hit this line of code' except client.CallActionError: _, __, tb3 = sys.exc_info() assert traceback.format_tb(tb1)[1:] == traceback.format_tb(tb2)[2:] assert traceback.format_tb(tb2)[2:] == traceback.format_tb(tb3)[2:]
def test_call_actions_future_success(self, mock_present_sounds): client = Client({}) future = client.call_actions_future( 'future_service', [{'action': 'present_sounds', 'body': {'foo': 'bar'}}], ) mock_present_sounds.assert_called_once_with({'foo': 'bar'}) assert future.exception() is None response = future.result() assert response.errors == [] assert response.actions[0].errors == [] assert response.actions[0].body == {'baz': 'qux'} assert response.context == {} assert future.result() is response assert future.result() is response assert future.exception() is None mock_present_sounds.assert_called_once_with({'foo': 'bar'})
def test_call_actions_parallel_future_success(self, mock_present_sounds, mock_past_sounds): client = Client({}) future = client.call_actions_parallel_future( 'future_service', [ {'action': 'present_sounds', 'body': {'where': 'here'}}, {'action': 'past_sounds', 'body': {'where': 'there'}}, ], ) mock_present_sounds.assert_called_once_with({'where': 'here'}) mock_past_sounds.assert_called_once_with({'where': 'there'}) assert isinstance(future.result(), types.GeneratorType) responses = list(future.result()) assert len(responses) == 2 assert responses[0].errors == [] assert responses[0].action == 'present_sounds' assert responses[0].body == {'when': 'present'} assert responses[1].errors == [] assert responses[1].action == 'past_sounds' assert responses[1].body == {'when': 'past'}
def test_call_actions(self): """Client.call_actions sends a valid request and returns a valid response without errors.""" action_request = [ { 'action': 'action_1', 'body': {}, }, { 'action': 'action_2', 'body': {}, }, ] client = Client(self.client_settings) for actions in (action_request, [ActionRequest(**a) for a in action_request]): response = client.call_actions(SERVICE_NAME, actions) self.assertTrue(isinstance(response, JobResponse)) self.assertTrue( all([isinstance(a, ActionResponse) for a in response.actions])) self.assertEqual(len(response.actions), 2) # ensure that the response is structured as expected self.assertEqual(response.actions[0].action, 'action_1') self.assertEqual(response.actions[0].body['foo'], 'bar') self.assertEqual(response.actions[1].action, 'action_2') self.assertEqual(response.actions[1].body['baz'], 3)
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)
def test_call_jobs_parallel_future_error(self, mock_present_sounds, mock_past_sounds): client = Client({}) future = client.call_jobs_parallel_future( [ {'service_name': 'future_service', 'actions': [ {'action': 'present_sounds', 'body': {'where': 'here'}}, ]}, {'service_name': 'future_service', 'actions': [ {'action': 'past_sounds', 'body': {'where': 'there'}}, ]}, ], ) mock_present_sounds.assert_called_once_with({'where': 'here'}) mock_past_sounds.assert_called_once_with({'where': 'there'}) with self.assertRaises(client.CallActionError) as error_context: assert future.result() first_exception = error_context.exception assert len(error_context.exception.actions[0].errors) == 1 error = error_context.exception.actions[0].errors[0] assert error.code == 'BROKEN' assert error.message == 'Broken, too' with self.assertRaises(client.CallActionError) as error_context: assert future.result() assert error_context.exception is first_exception assert len(error_context.exception.actions[0].errors) == 1 error = error_context.exception.actions[0].errors[0] assert error.code == 'BROKEN' assert error.message == 'Broken, too' with self.assertRaises(client.CallActionError) as error_context: assert future.result() assert error_context.exception is first_exception assert len(error_context.exception.actions[0].errors) == 1 error = error_context.exception.actions[0].errors[0] assert error.code == 'BROKEN' assert error.message == 'Broken, too' mock_present_sounds.assert_called_once_with({'where': 'here'}) mock_past_sounds.assert_called_once_with({'where': 'there'})
def setUp(self): self.client = Client({ SERVICE_NAME: { 'transport': { 'path': 'pysoa.test.stub_service:StubClientTransport', 'kwargs': { 'action_map': { 'action_1': {'body': {}}, }, }, } } })
def test_raises_only_error_codes_match(test_error, auth_missing_error): errors = [test_error, auth_missing_error] with raises_only_error_codes(['AUTH_MISSING', 'TEST']) as exc_info: raise Client.CallActionError( actions=[ActionResponse(action='', errors=errors)]) assert exc_info.soa_errors == errors
def test_check_client_settings_no_settings(self): client = Client({}) action_request = EnrichedActionRequest(action='status', body={}, switches=None, client=client) response = _CheckOtherServicesAction()(action_request) self.assertIsInstance(response, ActionResponse) self.assertEqual( { 'conformity': six.text_type(conformity.__version__), 'pysoa': six.text_type(pysoa.__version__), 'python': six.text_type(platform.python_version()), 'version': '8.71.2', 'healthcheck': { 'diagnostics': {}, 'errors': [], 'warnings': [] }, }, response.body, )
def test_send_receive_redis5_redis6_round_robin( pysoa_client: Client): # noqa: E999 for i in range(10): response = pysoa_client.call_action('echo', 'status', body={'verbose': False}) assert response.body, 'Iteration {} failed'.format(i)
def test_raises_field_errors_on_match(invalid_event_id_field_error): errors = [invalid_event_id_field_error] with raises_field_errors({'event_id': 'INVALID'}) as exc_info: raise Client.CallActionError( actions=[ActionResponse(action='', errors=errors)]) assert exc_info.soa_errors == errors
def test_raises_error_only_codes_unexpected_missing(test_error, auth_missing_error): errors = [test_error, auth_missing_error] with pytest.raises(pytest.raises.Exception): with raises_only_error_codes('UNAUTHORIZED'): raise Client.CallActionError( actions=[ActionResponse(action='', errors=errors)])
def test_call_action_job_error_not_raised(self): client = Client({ 'error_service': { 'transport': { 'path': 'pysoa.common.transport.local:LocalClientTransport', 'kwargs': { 'server_class': ErrorServer, 'server_settings': {}, }, }, } }) response = client.call_action('error_service', 'job_error', raise_job_errors=False) self.assertIsNotNone(response) self.assertEqual([Error(code='BAD_JOB', message='You are a bad job')], response)
def test_raises_call_action_error_on_error(test_error): errors = [test_error] with raises_call_action_error() as exc_info: raise Client.CallActionError( actions=[ActionResponse(action='', errors=errors)]) assert exc_info.soa_errors == errors
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
def test_raises_error_codes_on_match(test_error): errors = [test_error] with raises_error_codes('TEST') as exc_info: raise Client.CallActionError( actions=[ActionResponse(action='', errors=errors)]) assert exc_info.soa_errors == errors
def test_raises_error_only_codes_unexpected_field_error( invalid_event_id_field_error, auth_missing_error): errors = [invalid_event_id_field_error, auth_missing_error] with pytest.raises(pytest.raises.Exception): with raises_only_error_codes('AUTH_MISSING'): raise Client.CallActionError( actions=[ActionResponse(action='', errors=errors)])
def setUp(self): self.client = Client({ 'service_1': { 'transport': { 'path': 'pysoa.test.stub_service:StubClientTransport', 'kwargs': { 'action_map': { 'action_1': {'body': {'foo': 'bar'}}, 'action_2': {'body': {'baz': 3}}, }, }, }, }, 'service_2': { 'transport': { 'path': 'pysoa.test.stub_service:StubClientTransport', 'kwargs': { 'action_map': { 'action_3': {'body': {'cat': 'dog'}}, 'action_4': {'body': {'selected': True, 'count': 7}}, 'action_with_errors': { 'errors': [Error(code=ERROR_CODE_INVALID, message='Invalid input', field='foo')], }, }, }, }, }, 'error_service': { 'transport': { 'path': 'pysoa.common.transport.local:LocalClientTransport', 'kwargs': { 'server_class': ErrorServer, 'server_settings': {}, }, }, }, 'send_error_service': { 'transport': { 'path': 'tests.client.test_send_receive:SendErrorTransport', } }, 'receive_error_service': { 'transport': { 'path': 'tests.client.test_send_receive:ReceiveErrorTransport', } }, })
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'], )
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)])
def test_call_actions_future_error(self, mock_present_sounds): client = Client({}) future = client.call_actions_future( 'future_service', [{'action': 'present_sounds', 'body': {'foo': 'bar'}}], ) mock_present_sounds.assert_called_once_with({'foo': 'bar'}) with self.assertRaises(client.CallActionError) as error_context: raise future.exception() first_exception = error_context.exception assert len(error_context.exception.actions[0].errors) == 1 error = error_context.exception.actions[0].errors[0] assert error.code == 'BROKEN' assert error.message == 'Broken, dude' with self.assertRaises(client.CallActionError) as error_context: assert future.result() assert error_context.exception is first_exception assert len(error_context.exception.actions[0].errors) == 1 error = error_context.exception.actions[0].errors[0] assert error.code == 'BROKEN' assert error.message == 'Broken, dude' with self.assertRaises(client.CallActionError) as error_context: raise future.exception() assert error_context.exception is first_exception assert len(error_context.exception.actions[0].errors) == 1 error = error_context.exception.actions[0].errors[0] assert error.code == 'BROKEN' assert error.message == 'Broken, dude' mock_present_sounds.assert_called_once_with({'foo': 'bar'})
def test_raises_field_errors_unexpected_only(invalid_event_id_field_error, unknown_event_id_field_error): errors = [ invalid_event_id_field_error, unknown_event_id_field_error, ] with pytest.raises(pytest.raises.Exception): with raises_field_errors({'event_id': ['UNKNOWN']}, only=True): raise Client.CallActionError( actions=[ActionResponse(action='', errors=errors)])
def test_raises_only_field_errors_unexpected_error( auth_missing_error, invalid_organization_id_field_error): errors = [ auth_missing_error, invalid_organization_id_field_error, ] with pytest.raises(pytest.raises.Exception): with raises_only_field_errors({'organization_id': 'INVALID'}): raise Client.CallActionError( actions=[ActionResponse(action='', errors=errors)])
def test_raises_only_field_errors_unexpected_missing( unknown_event_id_field_error, invalid_organization_id_field_error): errors = [ unknown_event_id_field_error, invalid_organization_id_field_error, ] with pytest.raises(pytest.raises.Exception): with raises_only_field_errors({'event_id': 'MISSING'}): raise Client.CallActionError( actions=[ActionResponse(action='', errors=errors)])