From d171ff2052f9971ff133a168d3db268342e8fc28 Mon Sep 17 00:00:00 2001 From: Thomas NOEL Date: Wed, 24 Aug 2016 17:34:01 +0200 Subject: [PATCH] wscall: add action if err in json data or specific header (#12916) --- tests/test_workflows.py | 140 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/utilities.py | 8 +++ wcs/wf/wscall.py | 43 +++++++++++---- 3 files changed, 182 insertions(+), 9 deletions(-) diff --git a/tests/test_workflows.py b/tests/test_workflows.py index b5a1d51..6778c13 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -910,6 +910,146 @@ def test_webservice_call(pub): assert qs['evalme'] == [formdata.get_display_id()] assert qs['str'] == ['abcd'] +def test_webservice_call_error_handling(pub): + pub.substitutions.feed(MockSubstitutionVariables()) + + FormDef.wipe() + formdef = FormDef() + formdef.name = 'baz' + formdef.fields = [] + formdef.store() + + formdata = formdef.data_class()() + formdata.just_created() + formdata.store() + + item = WebserviceCallStatusItem() + item.url = 'http://remote.example.net/json-err1' + item.post = False + item.action_on_error_code = ':stop' + with pytest.raises(AbortActionException): + item.perform(formdata) + + item = WebserviceCallStatusItem() + item.url = 'http://remote.example.net/json-errheader1' + item.post = False + item.action_on_error_code = ':stop' + with pytest.raises(AbortActionException): + item.perform(formdata) + + item = WebserviceCallStatusItem() + item.url = 'http://remote.example.net/json-err0' + item.post = False + item.varname = 'xxx' + item.perform(formdata) + assert formdata.workflow_data['xxx_status'] == 200 + assert formdata.workflow_data['xxx_error_code'] == 0 + assert formdata.workflow_data['xxx_response'] == {'data': 'foo', 'err': 0} + assert formdata.workflow_data.get('xxx_time') + formdata.workflow_data = None + + item = WebserviceCallStatusItem() + item.url = 'http://remote.example.net/json-err0' + item.post = False + item.varname = 'xxx' + item.action_on_error_code = ':stop' + item.perform(formdata) + assert formdata.workflow_data['xxx_status'] == 200 + assert formdata.workflow_data['xxx_error_code'] == 0 + assert formdata.workflow_data['xxx_response'] == {'data': 'foo', 'err': 0} + assert formdata.workflow_data.get('xxx_time') + formdata.workflow_data = None + + item = WebserviceCallStatusItem() + item.url = 'http://remote.example.net/json-err1' + item.post = False + item.varname = 'xxx' + item.perform(formdata) + assert formdata.workflow_data['xxx_status'] == 200 + assert formdata.workflow_data['xxx_error_code'] == 1 + assert 'xxx_response' not in formdata.workflow_data + assert formdata.workflow_data.get('xxx_time') + formdata.workflow_data = None + + item = WebserviceCallStatusItem() + item.url = 'http://remote.example.net/json-err1' + item.post = False + item.varname = 'xxx' + item.action_on_error_code = ':stop' + with pytest.raises(AbortActionException): + item.perform(formdata) + assert formdata.workflow_data['xxx_status'] == 200 + assert formdata.workflow_data['xxx_error_code'] == 1 + assert formdata.workflow_data['xxx_error_response'] == {'data': '', 'err': 1} + assert 'xxx_response' not in formdata.workflow_data + assert formdata.workflow_data.get('xxx_time') + formdata.workflow_data = None + + item = WebserviceCallStatusItem() + item.url = 'http://remote.example.net/json-errheader0' + item.post = False + item.varname = 'xxx' + item.perform(formdata) + assert formdata.workflow_data['xxx_status'] == 200 + assert formdata.workflow_data['xxx_error_code'] == 0 + assert formdata.workflow_data['xxx_error_header'] == '0' + assert formdata.workflow_data['xxx_response'] == {'foo': 'bar'} + assert formdata.workflow_data.get('xxx_time') + formdata.workflow_data = None + + item = WebserviceCallStatusItem() + item.url = 'http://remote.example.net/json-errheader1' + item.post = False + item.varname = 'xxx' + item.perform(formdata) + assert formdata.workflow_data['xxx_status'] == 200 + assert formdata.workflow_data['xxx_error_code'] == 1 + assert formdata.workflow_data['xxx_error_header'] == '1' + assert formdata.workflow_data['xxx_error_response'] == {'foo': 'bar'} + assert 'xxx_response' not in formdata.workflow_data + assert formdata.workflow_data.get('xxx_time') + formdata.workflow_data = None + + item = WebserviceCallStatusItem() + item.url = 'http://remote.example.net/json-errheader1' + item.post = False + item.varname = 'xxx' + item.action_on_error_code = ':stop' + with pytest.raises(AbortActionException): + item.perform(formdata) + assert formdata.workflow_data['xxx_status'] == 200 + assert formdata.workflow_data['xxx_error_code'] == 1 + assert formdata.workflow_data['xxx_error_header'] == '1' + assert formdata.workflow_data['xxx_error_response'] == {'foo': 'bar'} + assert 'xxx_response' not in formdata.workflow_data + assert formdata.workflow_data.get('xxx_time') + formdata.workflow_data = None + + item = WebserviceCallStatusItem() + item.url = 'http://remote.example.net/xml-errheader' + item.post = False + item.varname = 'xxx' + item.response_type = 'attachment' + item.record_errors = True + item.perform(formdata) + assert formdata.workflow_data.get('xxx_status') == 200 + assert formdata.workflow_data.get('xxx_error_code') == 1 + assert formdata.workflow_data.get('xxx_error_header') == '1' + assert 'xxx_response' not in formdata.workflow_data + + item = WebserviceCallStatusItem() + item.url = 'http://remote.example.net/xml-errheader' + item.post = False + item.varname = 'xxx' + item.response_type = 'attachment' + item.record_errors = True + item.action_on_error_code = ':stop' + with pytest.raises(AbortActionException): + item.perform(formdata) + assert formdata.workflow_data.get('xxx_status') == 200 + assert formdata.workflow_data.get('xxx_error_code') == 1 + assert formdata.workflow_data.get('xxx_error_header') == '1' + assert 'xxx_response' not in formdata.workflow_data def test_timeout(pub): workflow = Workflow(name='timeout') diff --git a/tests/utilities.py b/tests/utilities.py index cf085c4..f77bd39 100644 --- a/tests/utilities.py +++ b/tests/utilities.py @@ -250,8 +250,16 @@ class HttpRequestsMocking(object): 'http://remote.example.net/404-json': (404, '{"err": 1}', None), 'http://remote.example.net/500': (500, 'internal server error', None), 'http://remote.example.net/json': (200, '{"foo": "bar"}', None), + 'http://remote.example.net/json-err0': (200, '{"data": "foo", "err": 0}', None), + 'http://remote.example.net/json-err1': (200, '{"data": "", "err": 1}', None), + 'http://remote.example.net/json-errheader0': (200, '{"foo": "bar"}', + {'x-error-code': '0'}), + 'http://remote.example.net/json-errheader1': (200, '{"foo": "bar"}', + {'x-error-code': '1'}), 'http://remote.example.net/xml': (200, '', {'content-type': 'text/xml'}), + 'http://remote.example.net/xml-errheader': (200, '', + {'content-type': 'text/xml', 'x-error-code': '1'}), }.get(base_url, (200, '', {})) class FakeResponse(object): diff --git a/wcs/wf/wscall.py b/wcs/wf/wscall.py index 2e8228b..9f8e3df 100644 --- a/wcs/wf/wscall.py +++ b/wcs/wf/wscall.py @@ -109,6 +109,7 @@ class WebserviceCallStatusItem(WorkflowStatusItem): action_on_5xx = ':stop' action_on_bad_data = ':pass' action_on_network_errors = ':stop' + action_on_error_code = ':pass' notify_on_errors = True record_errors = False @@ -133,7 +134,7 @@ class WebserviceCallStatusItem(WorkflowStatusItem): def get_parameters(self): return ('url', 'post', 'varname', 'request_signature_key', 'post_data', 'action_on_4xx', 'action_on_5xx', 'action_on_bad_data', - 'action_on_network_errors', 'notify_on_errors', + 'action_on_network_errors', 'action_on_error_code', 'notify_on_errors', 'record_errors', 'label', 'method', 'response_type', 'qs_data') @@ -199,7 +200,7 @@ class WebserviceCallStatusItem(WorkflowStatusItem): error_actions.extend([(x.id, _('Jump to %s') % x.name) for x in self.parent.parent.possible_status]) for attribute in ('action_on_4xx', 'action_on_5xx', 'action_on_network_errors', - 'action_on_bad_data'): + 'action_on_bad_data', 'action_on_error_code'): if not attribute in parameters: continue if attribute == 'action_on_bad_data': @@ -213,7 +214,8 @@ class WebserviceCallStatusItem(WorkflowStatusItem): 'action_on_4xx': _('Action on HTTP error 4xx'), 'action_on_5xx': _('Action on HTTP error 5xx'), 'action_on_bad_data': _('Action on non-JSON response'), - 'action_on_network_errors': _('Action on network errors') + 'action_on_network_errors': _('Action on network errors'), + 'action_on_error_code': _('Action on JSON or header error'), }.get(attribute) form.add(SingleSelectWidget, '%s%s' % (prefix, attribute), title=label, @@ -249,14 +251,34 @@ class WebserviceCallStatusItem(WorkflowStatusItem): self.action_on_error(self.action_on_network_errors, formdata, exc_info=sys.exc_info()) + error_code = 0 + error_code_header = response.getheader('x-error-code') + if error_code_header: + # result is good only if header value is '0' + try: + error_code = int(error_code_header) + except ValueError as e: + error_code = error_code_header + elif self.response_type == 'json': + try: + d = json_loads(data) + except (ValueError, TypeError) as e: + pass + else: + if isinstance(d, dict) and d.get('err'): + error_code = d['err'] + if self.varname: workflow_data = { '%s_status' % self.varname: status, '%s_time' % self.varname: datetime.datetime.now().isoformat(), + '%s_error_code' % self.varname: error_code, } + if error_code_header: + workflow_data['%s_error_header' % self.varname] = error_code_header if status in (204, 205): pass # not returning any content - elif (status // 100) == 2: + elif (status // 100) == 2 and error_code == 0: self.store_response(formdata, response, data, workflow_data) else: # on error, record data if it is JSON try: @@ -268,6 +290,8 @@ class WebserviceCallStatusItem(WorkflowStatusItem): formdata.update_workflow_data(workflow_data) formdata.store() + if error_code != 0: + self.action_on_error(self.action_on_error_code, formdata, response, data=data) if (status // 100) == 4: self.action_on_error(self.action_on_4xx, formdata, response, data=data) if (status // 100) == 5: @@ -284,10 +308,11 @@ class WebserviceCallStatusItem(WorkflowStatusItem): response, data=data, exc_info=sys.exc_info()) else: workflow_data['%s_response' % self.varname] = d - if isinstance(d.get('data'), dict) and d['data'].get('display_id'): - formdata.id_display = d.get('data', {}).get('display_id') - elif d.get('display_id'): - formdata.id_display = d.get('display_id') + if isinstance(d, dict): + if isinstance(d.get('data'), dict) and d['data'].get('display_id'): + formdata.id_display = d.get('data', {}).get('display_id') + elif d.get('display_id'): + formdata.id_display = d.get('display_id') else: # store result as attachment content_type = response.getheader('content-type') or '' if content_type: @@ -332,7 +357,7 @@ class WebserviceCallStatusItem(WorkflowStatusItem): def get_target_status(self): targets = [] for attribute in ('action_on_4xx', 'action_on_5xx', 'action_on_bad_data', - 'action_on_network_errors'): + 'action_on_network_errors', 'action_on_error_code'): value = getattr(self, attribute) if value in (':pass', ':stop'): continue -- 2.9.3