def test_call_local_action_no_server(self): r = EnrichedActionRequest(action='foo', body={'bar': 'baz'}) with pytest.raises(ActionError) as error_context: r.call_local_action('other_foo', {'color': 'red'}) assert error_context.value.errors[0].code == 'SERVER_ERROR' 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 == 'SERVER_ERROR'
def test_complex_status_body_none_works(self): action_request = EnrichedActionRequest(action='status', body=None, switches=None) response = _ComplexStatusAction()(action_request) self.assertIsInstance(response, ActionResponse) self.assertEqual( { 'build': 'complex_service-28381-7.8.9-16_04', 'conformity': six.text_type(conformity.__version__), 'healthcheck': { 'diagnostics': { 'check_good_called': True }, 'errors': [('ANOTHER_CODE', 'This is an error')], 'warnings': [('FIRST_CODE', 'First warning'), ('SECOND_CODE', 'Second warning')], }, 'pysoa': six.text_type(pysoa.__version__), 'python': six.text_type(platform.python_version()), 'version': '7.8.9', }, response.body, )
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_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, )
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 setUp(self): self.action = TestAction() self.action._return = {'boolean_field': True} self.action_request = EnrichedActionRequest( action='test_action', body={ 'string_field': 'a unicode string', }, )
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
def test_null_action_name(self): action = IntrospectionAction(FakeServerOne()) with self.assertRaises(ActionError) as error_context: action( EnrichedActionRequest(action='introspect', body={'action_name': None})) self.assertEqual(1, len(error_context.exception.errors)) self.assertEqual(ERROR_CODE_INVALID, error_context.exception.errors[0].code) self.assertEqual('action_name', error_context.exception.errors[0].field)
def test_action_two_no_switches(self): settings = {'foo': 'bar'} action = SwitchedActionTwo(cast(ServerSettings, settings)) response = action( EnrichedActionRequest( action='one', body={'building': 'Empire State Building'}, switches=[], )) self.assertEqual([], response.errors) self.assertEqual({'building_response': 'Empire State Building'}, response.body)
def test_action_one_switches_twelve_and_five(self): settings = {'baz': 'qux'} action = SwitchedActionOne(cast(ServerSettings, settings)) response = action( EnrichedActionRequest(action='one', body={'planet': 'Jupiter'}, switches=[12, 5])) self.assertEqual([], response.errors) self.assertEqual({ 'planet_response': 'Jupiter', 'settings': settings }, response.body)
def test_action_two_switch_seven(self): settings = {'baz': 'qux'} action = SwitchedActionTwo(cast(ServerSettings, settings)) response = action( EnrichedActionRequest(action='one', body={'animal': 'dog'}, switches=[7])) self.assertEqual([], response.errors) self.assertEqual({ 'animal_response': 'dog', 'settings': settings }, response.body)
def test_action_no_switches(self): settings = {'foo': 'bar'} action = SwitchedActionOne(cast(ServerSettings, settings)) response = action( EnrichedActionRequest(action='one', body={'animal': 'cat'}, switches=[])) self.assertEqual([], response.errors) self.assertEqual({ 'animal_response': 'cat', 'settings': settings }, response.body)
def test_action_one_switch_twelve(self): settings = {'foo': 'bar'} action = SwitchedActionOne(cast(ServerSettings, settings)) response = action( EnrichedActionRequest(action='one', body={'planet': 'Mars'}, switches=[12])) self.assertEqual([], response.errors) self.assertEqual({ 'planet_response': 'Mars', 'settings': settings }, response.body)
def test_action_two_switch_twelve(self): settings = {'foo': 'bar'} action = SwitchedActionTwo(settings) response = action( EnrichedActionRequest(action='one', body={'planet': 'Pluto'}, switches=[12])) self.assertEqual([], response.errors) self.assertEqual({ 'planet_response': 'Pluto', 'settings': settings }, response.body)
def test_complex_status_verbose_false_works(self): action_request = EnrichedActionRequest(action='status', body={'verbose': False}, switches=None) response = _ComplexStatusAction()(action_request) self.assertIsInstance(response, ActionResponse) self.assertEqual( { 'build': 'complex_service-28381-7.8.9-16_04', 'conformity': six.text_type(conformity.__version__), 'pysoa': six.text_type(pysoa.__version__), 'python': six.text_type(platform.python_version()), 'version': '7.8.9', }, response.body, )
def test_basic_status_works(self): action_request = EnrichedActionRequest(action='status', body={}, switches=None) response = StatusActionFactory('1.2.3', 'example_service-72-1.2.3-python3')()(action_request) self.assertIsInstance(response, ActionResponse) self.assertEqual( { 'build': 'example_service-72-1.2.3-python3', 'conformity': six.text_type(conformity.__version__), 'healthcheck': {'diagnostics': {}, 'errors': [], 'warnings': []}, 'pysoa': six.text_type(pysoa.__version__), 'python': six.text_type(platform.python_version()), 'version': '1.2.3', }, response.body, )
def test_whole_server_complex(self): action = IntrospectionAction(FakeServerTwo()) response = action(EnrichedActionRequest(action='introspect', body={})) self.assertEqual([], response.errors) self.assertEqual( { 'documentation': 'Instead, we should get this documentation', 'action_names': ['introspect', 'one', 'status', 'two'], 'actions': { 'introspect': { 'documentation': IntrospectionAction.description, 'request_schema': IntrospectionAction.request_schema.introspect(), 'response_schema': IntrospectionAction.response_schema.introspect(), }, 'status': { 'documentation': BaseStatusAction.description, 'request_schema': BaseStatusAction.request_schema.introspect(), 'response_schema': BaseStatusAction.response_schema.introspect(), }, 'one': { 'documentation': 'The real documentation', 'request_schema': None, 'response_schema': None, }, 'two': { 'documentation': 'Test action documentation', 'request_schema': FakeActionTwo.request_schema.introspect(), 'response_schema': FakeActionTwo.response_schema.introspect(), }, }, }, response.body, )
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
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
def test_single_action_simple(self): action = IntrospectionAction(FakeServerOne()) response = action( EnrichedActionRequest(action='introspect', body={'action_name': 'one'})) self.assertEqual([], response.errors) self.assertEqual( { 'action_names': ['one'], 'actions': { 'one': { 'documentation': 'The real documentation', 'request_schema': None, 'response_schema': None, }, }, }, response.body, )
def test_single_action_complex(self): action = IntrospectionAction(FakeServerTwo()) response = action( EnrichedActionRequest(action='introspect', body={'action_name': 'two'})) self.assertEqual([], response.errors) self.assertEqual( { 'action_names': ['two'], 'actions': { 'two': { 'documentation': 'Test action documentation', 'request_schema': FakeActionTwo.request_schema.introspect(), 'response_schema': FakeActionTwo.response_schema.introspect(), }, }, }, response.body, )
def test_whole_server_simple(self): action = IntrospectionAction(FakeServerOne()) response = action(EnrichedActionRequest(action='introspect', body={})) self.assertEqual([], response.errors) self.assertEqual( { 'documentation': 'This is the documentation we should get', 'action_names': ['introspect', 'one', 'status'], 'actions': { 'introspect': { 'documentation': IntrospectionAction.description, 'request_schema': IntrospectionAction.request_schema.introspect(), 'response_schema': IntrospectionAction.response_schema.introspect(), }, 'status': { 'documentation': BaseStatusAction.description, 'request_schema': BaseStatusAction.request_schema.introspect(), 'response_schema': BaseStatusAction.response_schema.introspect(), }, 'one': { 'documentation': 'The real documentation', 'request_schema': None, 'response_schema': None, }, }, }, response.body, )
def test_single_action_status_default(self): action = IntrospectionAction(FakeServerTwo()) response = action( EnrichedActionRequest(action='introspect', body={'action_name': 'status'})) self.assertEqual([], response.errors) self.assertEqual( { 'action_names': ['status'], 'actions': { 'status': { 'documentation': BaseStatusAction.description, 'request_schema': BaseStatusAction.request_schema.introspect(), 'response_schema': BaseStatusAction.response_schema.introspect(), }, }, }, response.body, )
def test_call_local_action_no_action(self): server = mock.MagicMock() server.action_class_map = {'unused_foo': mock.MagicMock()} 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 == 'UNKNOWN' assert error_context.value.errors[0].field == 'action' 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 == 'UNKNOWN' assert response.errors[0].field == 'action'
def test_single_action_switched(self): action = IntrospectionAction(FakeServerThree()) response = action( EnrichedActionRequest( action='introspect', body={'action_name': 'my_switched_action[DEFAULT]'}, )) self.assertEqual([], response.errors) self.assertEqual( { 'action_names': ['my_switched_action[DEFAULT]'], 'actions': { 'my_switched_action[DEFAULT]': { 'documentation': 'The real documentation', 'request_schema': None, 'response_schema': None, }, }, }, response.body, ) response = action( EnrichedActionRequest( action='introspect', body={'action_name': 'my_switched_action[switch:3]'}, )) self.assertEqual([], response.errors) self.assertEqual( { 'action_names': ['my_switched_action[switch:3]'], 'actions': { 'my_switched_action[switch:3]': { 'documentation': 'The real documentation', 'request_schema': None, 'response_schema': None, }, }, }, response.body, ) response = action( EnrichedActionRequest( action='introspect', body={'action_name': 'my_switched_action[switch:5]'}, )) self.assertEqual([], response.errors) self.assertEqual( { 'action_names': ['my_switched_action[switch:5]'], 'actions': { 'my_switched_action[switch:5]': { 'documentation': 'Test action documentation', 'request_schema': FakeActionTwo.request_schema.introspect(), 'response_schema': FakeActionTwo.response_schema.introspect(), }, }, }, response.body, ) response = action( EnrichedActionRequest( action='introspect', body={'action_name': 'your_switched_action'}, )) self.assertEqual([], response.errors) self.assertEqual( { 'action_names': [ 'your_switched_action[DEFAULT]', 'your_switched_action[switch:4]' ], 'actions': { 'your_switched_action[switch:4]': { 'documentation': 'The real documentation', 'request_schema': None, 'response_schema': None, }, 'your_switched_action[DEFAULT]': { 'documentation': 'Test action documentation', 'request_schema': FakeActionTwo.request_schema.introspect(), 'response_schema': FakeActionTwo.response_schema.introspect(), }, }, }, response.body, )
def execute_job(self, job_request): """ Processes and runs the action requests contained in the job and returns a `JobResponse`. :param job_request: The job request :type job_request: dict :return: A `JobResponse` object :rtype: JobResponse """ # 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'], async_event_loop=job_request['async_event_loop'], ) action_in_class_map = action_request.action in self.action_class_map if action_in_class_map or action_request.action in ('status', 'introspect'): # Get action to run if action_in_class_map: action = self.action_class_map[action_request.action](self.settings) elif action_request.action == 'introspect': from pysoa.server.action.introspection import IntrospectionAction action = IntrospectionAction(server=self) else: if not self._default_status_action_class: from pysoa.server.action.status import make_default_status_action_class self._default_status_action_class = make_default_status_action_class(self.__class__) action = self._default_status_action_class(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
def test_whole_server_switched(self): action = IntrospectionAction(FakeServerThree()) response = action(EnrichedActionRequest(action='introspect', body={})) self.assertEqual([], response.errors) self.assertEqual( { 'documentation': None, 'action_names': [ 'introspect', 'my_switched_action[DEFAULT]', 'my_switched_action[switch:5]', 'status', 'your_switched_action[DEFAULT]', 'your_switched_action[switch:4]', ], 'actions': { 'introspect': { 'documentation': IntrospectionAction.description, 'request_schema': IntrospectionAction.request_schema.introspect(), 'response_schema': IntrospectionAction.response_schema.introspect(), }, 'my_switched_action[DEFAULT]': { 'documentation': 'The real documentation', 'request_schema': None, 'response_schema': None, }, 'my_switched_action[switch:5]': { 'documentation': 'Test action documentation', 'request_schema': FakeActionTwo.request_schema.introspect(), 'response_schema': FakeActionTwo.response_schema.introspect(), }, 'status': { 'documentation': BaseStatusAction.description, 'request_schema': BaseStatusAction.request_schema.introspect(), 'response_schema': BaseStatusAction.response_schema.introspect(), }, 'your_switched_action[switch:4]': { 'documentation': 'The real documentation', 'request_schema': None, 'response_schema': None, }, 'your_switched_action[DEFAULT]': { 'documentation': 'Test action documentation', 'request_schema': FakeActionTwo.request_schema.introspect(), 'response_schema': FakeActionTwo.response_schema.introspect(), }, }, }, response.body, )