0001-wscall-add-action-if-error-in-application-12916.patch
tests/test_workflows.py | ||
---|---|---|
910 | 910 |
assert qs['evalme'] == [formdata.get_display_id()] |
911 | 911 |
assert qs['str'] == ['abcd'] |
912 | 912 | |
913 |
def test_webservice_call_error_handling(pub): |
|
914 |
pub.substitutions.feed(MockSubstitutionVariables()) |
|
915 | ||
916 |
FormDef.wipe() |
|
917 |
formdef = FormDef() |
|
918 |
formdef.name = 'baz' |
|
919 |
formdef.fields = [] |
|
920 |
formdef.store() |
|
921 | ||
922 |
formdata = formdef.data_class()() |
|
923 |
formdata.just_created() |
|
924 |
formdata.store() |
|
925 | ||
926 |
item = WebserviceCallStatusItem() |
|
927 |
item.url = 'http://remote.example.net/json-err1' |
|
928 |
item.post = False |
|
929 |
item.action_on_app_error = ':stop' |
|
930 |
with pytest.raises(AbortActionException): |
|
931 |
item.perform(formdata) |
|
932 | ||
933 |
item = WebserviceCallStatusItem() |
|
934 |
item.url = 'http://remote.example.net/json-errheader1' |
|
935 |
item.post = False |
|
936 |
item.action_on_app_error = ':stop' |
|
937 |
with pytest.raises(AbortActionException): |
|
938 |
item.perform(formdata) |
|
939 | ||
940 |
item = WebserviceCallStatusItem() |
|
941 |
item.url = 'http://remote.example.net/json-err0' |
|
942 |
item.post = False |
|
943 |
item.varname = 'xxx' |
|
944 |
item.perform(formdata) |
|
945 |
assert formdata.workflow_data['xxx_status'] == 200 |
|
946 |
assert formdata.workflow_data['xxx_app_error_code'] == 0 |
|
947 |
assert formdata.workflow_data['xxx_response'] == {'data': 'foo', 'err': 0} |
|
948 |
assert formdata.workflow_data.get('xxx_time') |
|
949 |
formdata.workflow_data = None |
|
950 | ||
951 |
item = WebserviceCallStatusItem() |
|
952 |
item.url = 'http://remote.example.net/json-err0' |
|
953 |
item.post = False |
|
954 |
item.varname = 'xxx' |
|
955 |
item.action_on_app_error = ':stop' |
|
956 |
item.perform(formdata) |
|
957 |
assert formdata.workflow_data['xxx_status'] == 200 |
|
958 |
assert formdata.workflow_data['xxx_app_error_code'] == 0 |
|
959 |
assert formdata.workflow_data['xxx_response'] == {'data': 'foo', 'err': 0} |
|
960 |
assert formdata.workflow_data.get('xxx_time') |
|
961 |
formdata.workflow_data = None |
|
962 | ||
963 |
item = WebserviceCallStatusItem() |
|
964 |
item.url = 'http://remote.example.net/json-err1' |
|
965 |
item.post = False |
|
966 |
item.varname = 'xxx' |
|
967 |
item.perform(formdata) |
|
968 |
assert formdata.workflow_data['xxx_status'] == 200 |
|
969 |
assert formdata.workflow_data['xxx_app_error_code'] == 1 |
|
970 |
assert 'xxx_response' not in formdata.workflow_data |
|
971 |
assert formdata.workflow_data.get('xxx_time') |
|
972 |
formdata.workflow_data = None |
|
973 | ||
974 |
item = WebserviceCallStatusItem() |
|
975 |
item.url = 'http://remote.example.net/json-err1' |
|
976 |
item.post = False |
|
977 |
item.varname = 'xxx' |
|
978 |
item.action_on_app_error = ':stop' |
|
979 |
with pytest.raises(AbortActionException): |
|
980 |
item.perform(formdata) |
|
981 |
assert formdata.workflow_data['xxx_status'] == 200 |
|
982 |
assert formdata.workflow_data['xxx_app_error_code'] == 1 |
|
983 |
assert formdata.workflow_data['xxx_error_response'] == {'data': '', 'err': 1} |
|
984 |
assert 'xxx_response' not in formdata.workflow_data |
|
985 |
assert formdata.workflow_data.get('xxx_time') |
|
986 |
formdata.workflow_data = None |
|
987 | ||
988 |
item = WebserviceCallStatusItem() |
|
989 |
item.url = 'http://remote.example.net/json-errheader0' |
|
990 |
item.post = False |
|
991 |
item.varname = 'xxx' |
|
992 |
item.perform(formdata) |
|
993 |
assert formdata.workflow_data['xxx_status'] == 200 |
|
994 |
assert formdata.workflow_data['xxx_app_error_code'] == 0 |
|
995 |
assert formdata.workflow_data['xxx_app_error_header'] == '0' |
|
996 |
assert formdata.workflow_data['xxx_response'] == {'foo': 'bar'} |
|
997 |
assert formdata.workflow_data.get('xxx_time') |
|
998 |
formdata.workflow_data = None |
|
999 | ||
1000 |
item = WebserviceCallStatusItem() |
|
1001 |
item.url = 'http://remote.example.net/json-errheader1' |
|
1002 |
item.post = False |
|
1003 |
item.varname = 'xxx' |
|
1004 |
item.perform(formdata) |
|
1005 |
assert formdata.workflow_data['xxx_status'] == 200 |
|
1006 |
assert formdata.workflow_data['xxx_app_error_code'] == 1 |
|
1007 |
assert formdata.workflow_data['xxx_app_error_header'] == '1' |
|
1008 |
assert formdata.workflow_data['xxx_error_response'] == {'foo': 'bar'} |
|
1009 |
assert 'xxx_response' not in formdata.workflow_data |
|
1010 |
assert formdata.workflow_data.get('xxx_time') |
|
1011 |
formdata.workflow_data = None |
|
1012 | ||
1013 |
item = WebserviceCallStatusItem() |
|
1014 |
item.url = 'http://remote.example.net/json-errheader1' |
|
1015 |
item.post = False |
|
1016 |
item.varname = 'xxx' |
|
1017 |
item.action_on_app_error = ':stop' |
|
1018 |
with pytest.raises(AbortActionException): |
|
1019 |
item.perform(formdata) |
|
1020 |
assert formdata.workflow_data['xxx_status'] == 200 |
|
1021 |
assert formdata.workflow_data['xxx_app_error_code'] == 1 |
|
1022 |
assert formdata.workflow_data['xxx_app_error_header'] == '1' |
|
1023 |
assert formdata.workflow_data['xxx_error_response'] == {'foo': 'bar'} |
|
1024 |
assert 'xxx_response' not in formdata.workflow_data |
|
1025 |
assert formdata.workflow_data.get('xxx_time') |
|
1026 |
formdata.workflow_data = None |
|
1027 | ||
1028 |
item = WebserviceCallStatusItem() |
|
1029 |
item.url = 'http://remote.example.net/xml-errheader' |
|
1030 |
item.post = False |
|
1031 |
item.varname = 'xxx' |
|
1032 |
item.response_type = 'attachment' |
|
1033 |
item.record_errors = True |
|
1034 |
item.perform(formdata) |
|
1035 |
assert formdata.workflow_data.get('xxx_status') == 200 |
|
1036 |
assert formdata.workflow_data.get('xxx_app_error_code') == 1 |
|
1037 |
assert formdata.workflow_data.get('xxx_app_error_header') == '1' |
|
1038 |
assert 'xxx_response' not in formdata.workflow_data |
|
1039 | ||
1040 |
item = WebserviceCallStatusItem() |
|
1041 |
item.url = 'http://remote.example.net/xml-errheader' |
|
1042 |
item.post = False |
|
1043 |
item.varname = 'xxx' |
|
1044 |
item.response_type = 'attachment' |
|
1045 |
item.record_errors = True |
|
1046 |
item.action_on_app_error = ':stop' |
|
1047 |
with pytest.raises(AbortActionException): |
|
1048 |
item.perform(formdata) |
|
1049 |
assert formdata.workflow_data.get('xxx_status') == 200 |
|
1050 |
assert formdata.workflow_data.get('xxx_app_error_code') == 1 |
|
1051 |
assert formdata.workflow_data.get('xxx_app_error_header') == '1' |
|
1052 |
assert 'xxx_response' not in formdata.workflow_data |
|
913 | 1053 | |
914 | 1054 |
def test_timeout(pub): |
915 | 1055 |
workflow = Workflow(name='timeout') |
tests/utilities.py | ||
---|---|---|
250 | 250 |
'http://remote.example.net/404-json': (404, '{"err": 1}', None), |
251 | 251 |
'http://remote.example.net/500': (500, 'internal server error', None), |
252 | 252 |
'http://remote.example.net/json': (200, '{"foo": "bar"}', None), |
253 |
'http://remote.example.net/json-err0': (200, '{"data": "foo", "err": 0}', None), |
|
254 |
'http://remote.example.net/json-err1': (200, '{"data": "", "err": 1}', None), |
|
255 |
'http://remote.example.net/json-errheader0': (200, '{"foo": "bar"}', |
|
256 |
{'x-error-code': '0'}), |
|
257 |
'http://remote.example.net/json-errheader1': (200, '{"foo": "bar"}', |
|
258 |
{'x-error-code': '1'}), |
|
253 | 259 |
'http://remote.example.net/xml': (200, '<?xml version="1.0"><foo/>', |
254 | 260 |
{'content-type': 'text/xml'}), |
261 |
'http://remote.example.net/xml-errheader': (200, '<?xml version="1.0"><foo/>', |
|
262 |
{'content-type': 'text/xml', 'x-error-code': '1'}), |
|
255 | 263 |
}.get(base_url, (200, '', {})) |
256 | 264 | |
257 | 265 |
class FakeResponse(object): |
wcs/wf/wscall.py | ||
---|---|---|
105 | 105 |
_method = None |
106 | 106 |
response_type = 'json' |
107 | 107 | |
108 |
action_on_app_error = ':pass' |
|
108 | 109 |
action_on_4xx = ':stop' |
109 | 110 |
action_on_5xx = ':stop' |
110 | 111 |
action_on_bad_data = ':pass' |
... | ... | |
132 | 133 | |
133 | 134 |
def get_parameters(self): |
134 | 135 |
return ('url', 'post', 'varname', 'request_signature_key', 'post_data', |
135 |
'action_on_4xx', 'action_on_5xx', 'action_on_bad_data', |
|
136 |
'action_on_app_error', 'action_on_4xx', 'action_on_5xx', 'action_on_bad_data',
|
|
136 | 137 |
'action_on_network_errors', 'notify_on_errors', |
137 | 138 |
'record_errors', 'label', 'method', 'response_type', |
138 | 139 |
'qs_data') |
... | ... | |
198 | 199 |
error_actions = [(':stop', _('Stop')), (':pass', _('Ignore'))] |
199 | 200 |
error_actions.extend([(x.id, _('Jump to %s') % x.name) for x in |
200 | 201 |
self.parent.parent.possible_status]) |
201 |
for attribute in ('action_on_4xx', 'action_on_5xx', 'action_on_network_errors',
|
|
202 |
'action_on_bad_data'): |
|
202 |
for attribute in ('action_on_app_error', 'action_on_4xx', 'action_on_5xx',
|
|
203 |
'action_on_network_errors', 'action_on_bad_data'):
|
|
203 | 204 |
if not attribute in parameters: |
204 | 205 |
continue |
205 | 206 |
if attribute == 'action_on_bad_data': |
... | ... | |
210 | 211 |
else: |
211 | 212 |
attrs = {} |
212 | 213 |
label = { |
214 |
'action_on_app_error': _('Action on application error'), |
|
213 | 215 |
'action_on_4xx': _('Action on HTTP error 4xx'), |
214 | 216 |
'action_on_5xx': _('Action on HTTP error 5xx'), |
215 | 217 |
'action_on_bad_data': _('Action on non-JSON response'), |
... | ... | |
249 | 251 |
self.action_on_error(self.action_on_network_errors, formdata, |
250 | 252 |
exc_info=sys.exc_info()) |
251 | 253 | |
254 |
app_error_code = 0 |
|
255 |
app_error_code_header = response.getheader('x-error-code') |
|
256 |
if app_error_code_header: |
|
257 |
# result is good only if header value is '0' |
|
258 |
try: |
|
259 |
app_error_code = int(app_error_code_header) |
|
260 |
except ValueError as e: |
|
261 |
app_error_code = app_error_code_header |
|
262 |
elif self.response_type == 'json': |
|
263 |
try: |
|
264 |
d = json_loads(data) |
|
265 |
except (ValueError, TypeError) as e: |
|
266 |
pass |
|
267 |
else: |
|
268 |
if isinstance(d, dict) and d.get('err'): |
|
269 |
app_error_code = d['err'] |
|
270 | ||
252 | 271 |
if self.varname: |
253 | 272 |
workflow_data = { |
254 | 273 |
'%s_status' % self.varname: status, |
255 | 274 |
'%s_time' % self.varname: datetime.datetime.now().isoformat(), |
275 |
'%s_app_error_code' % self.varname: app_error_code, |
|
256 | 276 |
} |
277 |
if app_error_code_header: |
|
278 |
workflow_data['%s_app_error_header' % self.varname] = app_error_code_header |
|
257 | 279 |
if status in (204, 205): |
258 | 280 |
pass # not returning any content |
259 |
elif (status // 100) == 2: |
|
281 |
elif (status // 100) == 2 and app_error_code == 0:
|
|
260 | 282 |
self.store_response(formdata, response, data, workflow_data) |
261 | 283 |
else: # on error, record data if it is JSON |
262 | 284 |
try: |
... | ... | |
268 | 290 |
formdata.update_workflow_data(workflow_data) |
269 | 291 |
formdata.store() |
270 | 292 | |
293 |
if app_error_code != 0: |
|
294 |
self.action_on_error(self.action_on_app_error, formdata, response, data=data) |
|
271 | 295 |
if (status // 100) == 4: |
272 | 296 |
self.action_on_error(self.action_on_4xx, formdata, response, data=data) |
273 | 297 |
if (status // 100) == 5: |
... | ... | |
284 | 308 |
response, data=data, exc_info=sys.exc_info()) |
285 | 309 |
else: |
286 | 310 |
workflow_data['%s_response' % self.varname] = d |
287 |
if isinstance(d.get('data'), dict) and d['data'].get('display_id'): |
|
288 |
formdata.id_display = d.get('data', {}).get('display_id') |
|
289 |
elif d.get('display_id'): |
|
290 |
formdata.id_display = d.get('display_id') |
|
311 |
if isinstance(d, dict): |
|
312 |
if isinstance(d.get('data'), dict) and d['data'].get('display_id'): |
|
313 |
formdata.id_display = d.get('data', {}).get('display_id') |
|
314 |
elif d.get('display_id'): |
|
315 |
formdata.id_display = d.get('display_id') |
|
291 | 316 |
else: # store result as attachment |
292 | 317 |
content_type = response.getheader('content-type') or '' |
293 | 318 |
if content_type: |
... | ... | |
331 | 356 | |
332 | 357 |
def get_target_status(self): |
333 | 358 |
targets = [] |
334 |
for attribute in ('action_on_4xx', 'action_on_5xx', 'action_on_bad_data',
|
|
335 |
'action_on_network_errors'): |
|
359 |
for attribute in ('action_on_app_error', 'action_on_4xx', 'action_on_5xx',
|
|
360 |
'action_on_bad_data', 'action_on_network_errors'):
|
|
336 | 361 |
value = getattr(self, attribute) |
337 | 362 |
if value in (':pass', ':stop'): |
338 | 363 |
continue |
339 |
- |