wcs/tests/workflow/test_wscall.py

1161 lines
42 KiB
Python

import base64
import io
import json
import urllib
import pytest
import responses
from quixote import cleanup, get_publisher
from wcs.blocks import BlockDef
from wcs.fields import BlockField, BoolField, FileField, ItemField, ItemsField, StringField
from wcs.formdef import FormDef
from wcs.qommon.http_request import HTTPRequest
from wcs.wf.wscall import JournalWsCallErrorPart, WebserviceCallStatusItem
from wcs.workflows import (
AbortActionException,
AttachmentEvolutionPart,
ContentSnapshotPart,
Workflow,
WorkflowBackofficeFieldsFormDef,
)
from ..utilities import MockSubstitutionVariables, clean_temporary_pub, create_temporary_pub
def setup_module(module):
cleanup()
def teardown_module(module):
clean_temporary_pub()
@pytest.fixture
def pub(request):
pub = create_temporary_pub()
pub.cfg['language'] = {'language': 'en'}
pub.write_cfg()
req = HTTPRequest(None, {'SERVER_NAME': 'example.net', 'SCRIPT_NAME': ''})
req.response.filter = {}
req._user = None
pub._set_request(req)
pub.set_config(req)
return pub
def test_wscall_record_errors(pub):
pub.user_class.wipe()
FormDef.wipe()
formdef = FormDef()
formdef.name = 'test'
formdef.fields = []
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.just_created()
formdata.store()
wscall = WebserviceCallStatusItem()
wscall.url = 'http://test/'
wscall.varname = 'varname'
wscall.action_on_4xx = ':pass'
wscall.record_errors = True
# error with json data (stored as a string)
with responses.RequestsMock() as rsps:
rsps.get('http://test', status=404, json={'err': 1})
wscall.perform(formdata)
assert len([x for x in formdata.evolution[-1].parts if isinstance(x, JournalWsCallErrorPart)]) == 1
assert formdata.evolution[-1].parts[-1].get_json_export_dict() == {
'data': '{"err": 1}',
'label': None,
'summary': '404 Not Found',
'type': 'wscall-error',
}
# error with bytes that can be stored as string
formdata.evolution[-1].parts = []
formdata.store()
with responses.RequestsMock() as rsps:
rsps.get('http://test', status=404, body=b'test bytes')
wscall.perform(formdata)
assert len([x for x in formdata.evolution[-1].parts if isinstance(x, JournalWsCallErrorPart)]) == 1
assert formdata.evolution[-1].parts[-1].get_json_export_dict() == {
'type': 'wscall-error',
'summary': '404 Not Found',
'label': None,
'data': 'test bytes',
}
# error with bytes that cannot be stored as string
formdata.evolution[-1].parts = []
formdata.store()
with responses.RequestsMock() as rsps:
rsps.get('http://test', status=404, body=b'\xf1')
wscall.perform(formdata)
assert len([x for x in formdata.evolution[-1].parts if isinstance(x, JournalWsCallErrorPart)]) == 1
assert formdata.evolution[-1].parts[-1].get_json_export_dict() == {
'type': 'wscall-error',
'summary': '404 Not Found',
'label': None,
'data_b64': '8Q==\n',
}
# application error
pub.loggederror_class.wipe()
with responses.RequestsMock() as rsps:
rsps.get('http://test', status=200, body=b'{"err": 1, "err_desc": "some error"}')
wscall.perform(formdata)
assert formdata.evolution[-1].parts[-1].get_json_export_dict() == {
'data': '{"err": 1, "err_desc": "some error"}',
'label': None,
'summary': 'err: 1, err_desc: some error',
'type': 'wscall-error',
}
assert pub.loggederror_class.count() == 1
assert pub.loggederror_class.select()[0].summary == '[WSCALL] err: 1, err_desc: some error'
pub.loggederror_class.wipe()
with responses.RequestsMock() as rsps:
rsps.get('http://test', status=200, body=b'{"err": 1}')
wscall.perform(formdata)
assert formdata.evolution[-1].parts[-1].get_json_export_dict() == {
'data': '{"err": 1}',
'label': None,
'summary': 'err: 1',
'type': 'wscall-error',
}
assert pub.loggederror_class.count() == 1
assert pub.loggederror_class.select()[0].summary == '[WSCALL] err: 1'
pub.loggederror_class.wipe()
with responses.RequestsMock() as rsps:
rsps.get('http://test', status=200, body=b'xxx', headers={'x-error-code': 'X'})
wscall.perform(formdata)
assert formdata.evolution[-1].parts[-1].get_json_export_dict() == {
'data': 'xxx',
'label': None,
'summary': 'err: X',
'type': 'wscall-error',
}
assert pub.loggederror_class.count() == 1
assert pub.loggederror_class.select()[0].summary == '[WSCALL] err: X'
pub.loggederror_class.wipe()
# error with payload that cannot be converted to JSON
pub.loggederror_class.wipe()
formdata.evolution[-1].parts = []
formdata.store()
wscall.method = 'POST'
wscall.post_data = {'xxx': '=Ellipsis'}
wscall.perform(formdata)
assert pub.loggederror_class.count() == 1
assert (
formdata.workflow_data['varname_payload_error'] == 'Object of type ellipsis is not JSON serializable'
)
assert (
pub.loggederror_class.select()[0].summary
== 'Invalid payload (Object of type ellipsis is not JSON serializable)'
)
def test_wscall_post_formdata(pub):
pub.user_class.wipe()
FormDef.wipe()
workflow = Workflow(name='wf')
st1 = workflow.add_status('Status1', 'st1')
jump = st1.add_action('jump')
jump.status = 'st2'
st2 = workflow.add_status('Status2', 'st2')
wscall = st2.add_action('webservice_call')
wscall.url = 'http://test/'
wscall.varname = 'varname'
wscall.action_on_4xx = ':pass'
wscall.post = True
wscall.record_errors = True
workflow.store()
formdef = FormDef()
formdef.name = 'test'
formdef.fields = [
StringField(id='1', label='string'),
]
formdef.workflow = workflow
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.just_created()
formdata.store()
with responses.RequestsMock() as rsps:
rsps.post('http://test', status=200, json={'err': 0})
formdata.perform_workflow()
assert json.loads(rsps.calls[0].request.body)['workflow']['status']['id'] == 'st2'
def test_wscall_publik_caller_url(pub):
pub.user_class.wipe()
FormDef.wipe()
Workflow.wipe()
workflow = Workflow(name='wf')
st1 = workflow.add_status('Status1', 'st1')
jump = st1.add_action('jump')
jump.status = 'st2'
st2 = workflow.add_status('Status2', 'st2')
wscall = st2.add_action('webservice_call')
wscall.url = 'https://passerelle.invalid/some-endoint/'
wscall.varname = 'varname'
formdef = FormDef()
formdef.name = 'test'
formdef.fields = [
StringField(id='1', label='string'),
]
formdef.workflow = workflow
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.just_created()
formdata.store()
with responses.RequestsMock() as rsps:
rsps.get('https://passerelle.invalid/some-endoint/', status=200, json={'err': 0})
formdata.perform_workflow()
assert rsps.calls[0].request.headers['Publik-Caller-URL'] == formdata.get_backoffice_url()
def test_webservice_call(http_requests, pub):
pub.substitutions.feed(MockSubstitutionVariables())
wf = Workflow(name='wf1')
st1 = wf.add_status('Status1', 'st1')
wf.add_status('StatusErr', 'sterr')
wf.store()
FormDef.wipe()
formdef = FormDef()
formdef.name = 'baz'
formdef.fields = []
formdef.workflow_id = wf.id
formdef.store()
formdata = formdef.data_class()()
formdata.just_created()
formdata.store()
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
item.perform(formdata)
assert http_requests.get_last('url') == 'http://remote.example.net/'
assert http_requests.get_last('method') == 'GET'
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
item.post = True
item.perform(formdata)
assert http_requests.get_last('url') == 'http://remote.example.net/'
assert http_requests.get_last('method') == 'POST'
payload = json.loads(http_requests.get_last('body'))
assert payload['url'] == 'http://example.net/baz/%s/' % formdata.id
assert payload['display_id'] == formdata.get_display_id()
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
item.post_data = {
'str': 'abcd',
'one': '=1',
'django': '{{ form_number }}',
'evalme': '=form_number',
'error': '=1=3',
}
pub.substitutions.feed(formdata)
item.perform(formdata)
assert http_requests.get_last('url') == 'http://remote.example.net/'
assert http_requests.get_last('method') == 'POST'
payload = json.loads(http_requests.get_last('body'))
assert payload == {
'one': 1,
'str': 'abcd',
'evalme': formdata.get_display_id(),
'django': formdata.get_display_id(),
}
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
item.post = True
item.post_data = {
'str': 'abcd',
'one': '=1',
'decimal': '=Decimal(2)',
'evalme': '=form_number',
'error': '=1=3',
}
pub.substitutions.feed(formdata)
item.perform(formdata)
assert http_requests.get_last('url') == 'http://remote.example.net/'
assert http_requests.get_last('method') == 'POST'
payload = json.loads(http_requests.get_last('body'))
assert payload['extra'] == {'one': 1, 'str': 'abcd', 'decimal': '2', 'evalme': formdata.get_display_id()}
assert payload['url'] == 'http://example.net/baz/%s/' % formdata.id
assert payload['display_id'] == formdata.get_display_id()
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/json'
item.varname = 'xxx'
item.perform(formdata)
assert formdata.workflow_data['xxx_status'] == 200
assert formdata.workflow_data['xxx_response'] == {'foo': 'bar'}
assert formdata.workflow_data.get('xxx_time')
get_publisher().substitutions.reset()
get_publisher().substitutions.feed(formdata)
substvars = get_publisher().substitutions.get_context_variables(mode='lazy')
assert str(substvars['xxx_status']) == '200'
assert 'xxx_status' in substvars.get_flat_keys()
assert str(substvars['xxx_response_foo']) == 'bar'
assert 'xxx_response_foo' in substvars.get_flat_keys()
pub.substitutions.reset()
pub.substitutions.feed(MockSubstitutionVariables())
formdata.workflow_data = None
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
item.request_signature_key = 'xxx'
item.perform(formdata)
assert 'signature=' in http_requests.get_last('url')
assert http_requests.get_last('method') == 'GET'
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
item.request_signature_key = '{{ doesntexist }}'
item.perform(formdata)
assert 'signature=' not in http_requests.get_last('url')
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
item.request_signature_key = '{{ empty }}'
item.perform(formdata)
assert 'signature=' not in http_requests.get_last('url')
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
item.request_signature_key = '[empty]'
item.perform(formdata)
assert 'signature=' not in http_requests.get_last('url')
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
item.request_signature_key = '{{ bar }}'
item.perform(formdata)
assert 'signature=' in http_requests.get_last('url')
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
item.request_signature_key = '[bar]'
item.perform(formdata)
assert 'signature=' in http_requests.get_last('url')
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/204'
item.varname = 'xxx'
item.perform(formdata)
assert formdata.workflow_data.get('xxx_status') == 204
assert formdata.workflow_data.get('xxx_time')
assert 'xxx_response' not in formdata.workflow_data
assert 'xxx_error_response' not in formdata.workflow_data
formdata.workflow_data = None
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/404'
item.varname = 'xxx'
with pytest.raises(AbortActionException):
item.perform(formdata)
assert formdata.workflow_data.get('xxx_status') == 404
assert formdata.workflow_data.get('xxx_time')
assert 'xxx_error_response' not in formdata.workflow_data
formdata.workflow_data = None
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/404-json'
item.varname = 'xxx'
with pytest.raises(AbortActionException):
item.perform(formdata)
assert formdata.workflow_data.get('xxx_status') == 404
assert formdata.workflow_data.get('xxx_error_response') == {'err': 'not-found'}
assert formdata.workflow_data.get('xxx_time')
assert 'xxx_response' not in formdata.workflow_data
formdata.workflow_data = None
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/404'
item.action_on_4xx = ':pass'
item.perform(formdata)
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/500'
with pytest.raises(AbortActionException):
item.perform(formdata)
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/500'
item.action_on_5xx = ':pass'
item.perform(formdata)
item = WebserviceCallStatusItem()
item.parent = st1
assert item.get_jump_label(st1.id) == 'Webservice'
assert item.get_jump_label('sterr') == 'Error calling webservice'
item.label = 'Plop'
assert item.get_jump_label(st1.id) == 'Webservice "Plop"'
assert item.get_jump_label('sterr') == 'Error calling webservice "Plop"'
item.url = 'http://remote.example.net/500'
item.action_on_5xx = 'sterr' # jump to status
formdata.status = 'wf-st1'
formdata.store()
with pytest.raises(AbortActionException):
item.perform(formdata)
assert formdata.status == 'wf-sterr'
pub.loggederror_class.wipe()
item.action_on_5xx = 'stdeleted' # removed status
formdata.status = 'wf-st1'
formdata.store()
with pytest.raises(AbortActionException):
item.perform(formdata)
assert formdata.status == 'wf-st1' # unknown status acts like :stop
assert pub.loggederror_class.count() == 1
error = pub.loggederror_class.select()[0]
assert 'reference-to-invalid-status-stdeleted-in-workflow' in error.tech_id
assert error.occurences_count == 1
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/xml'
item.perform(formdata)
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/xml'
item.varname = 'xxx'
item.action_on_bad_data = ':stop'
with pytest.raises(AbortActionException):
item.perform(formdata)
assert formdata.workflow_data.get('xxx_status') == 200
assert 'xxx_response' not in formdata.workflow_data
assert 'xxx_error_response' not in formdata.workflow_data
formdata.workflow_data = None
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/404'
item.record_errors = True
item.action_on_4xx = ':stop'
with pytest.raises(AbortActionException):
item.perform(formdata)
assert formdata.evolution[-1].parts[-1].summary == '404 Not Found'
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/xml'
item.varname = 'xxx'
item.action_on_bad_data = ':stop'
item.record_errors = True
with pytest.raises(AbortActionException):
item.perform(formdata)
assert (
formdata.evolution[-1].parts[-1].summary
== 'json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)\n'
)
assert formdata.workflow_data.get('xxx_status') == 200
assert formdata.workflow_data.get('xxx_time')
assert 'xxx_error_response' not in formdata.workflow_data
formdata.workflow_data = None
# check storing response as attachment
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/xml'
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_content_type') == 'text/xml'
attachment = formdata.evolution[-1].parts[-1]
assert isinstance(attachment, AttachmentEvolutionPart)
assert attachment.base_filename == 'xxx.xml'
assert attachment.content_type == 'text/xml'
attachment.fp.seek(0)
assert attachment.fp.read(5) == b'<?xml'
formdata.workflow_data = None
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/400-json'
item.record_errors = True
item.action_on_4xx = ':stop'
with pytest.raises(AbortActionException):
item.perform(formdata)
assert formdata.evolution[-1].parts[-1].is_hidden() # not displayed in front
req = HTTPRequest(None, {'SERVER_NAME': 'example.net', 'SCRIPT_NAME': '/backoffice/'})
pub._set_request(req)
assert not formdata.evolution[-1].parts[-1].is_hidden()
rendered = formdata.evolution[-1].parts[-1].view()
assert 'Error during webservice call' in str(rendered)
assert 'Error Code: 1' in str(rendered)
assert 'Error Description: :(' in str(rendered)
item.label = 'do that'
with pytest.raises(AbortActionException):
item.perform(formdata)
rendered = formdata.evolution[-1].parts[-1].view()
assert 'Error during webservice call &quot;do that&quot;' in str(rendered)
item = WebserviceCallStatusItem()
item.method = 'GET'
item.url = 'http://remote.example.net?in_url=1'
item.qs_data = {
'str': 'abcd',
'one': '=1',
'evalme': '=form_number',
'django': '{{ form_number }}',
'ezt': '[form_number]',
'error': '=1=3',
'in_url': '2',
}
pub.substitutions.feed(formdata)
item.perform(formdata)
assert http_requests.get_last('method') == 'GET'
qs = urllib.parse.parse_qs(http_requests.get_last('url').split('?')[1])
assert set(qs.keys()) == {'in_url', 'str', 'one', 'evalme', 'django', 'ezt'}
assert qs['in_url'] == ['1', '2']
assert qs['one'] == ['1']
assert qs['evalme'] == [formdata.get_display_id()]
assert qs['django'] == [formdata.get_display_id()]
assert qs['ezt'] == [formdata.get_display_id()]
assert qs['str'] == ['abcd']
item = WebserviceCallStatusItem()
item.method = 'DELETE'
item.post = False
item.post_data = {'str': 'efgh', 'one': '=3', 'evalme': '=form_number', 'error': '=1=3'}
item.url = 'http://remote.example.net/json'
pub.substitutions.feed(formdata)
item.perform(formdata)
assert http_requests.get_last('url') == 'http://remote.example.net/json'
assert http_requests.get_last('method') == 'DELETE'
payload = json.loads(http_requests.get_last('body'))
assert payload == {'one': 3, 'str': 'efgh', 'evalme': formdata.get_display_id()}
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
item.method = 'PUT'
item.post = False
item.post_data = {'str': 'abcd', 'one': '=1', 'evalme': '=form_number', 'error': '=1=3'}
pub.substitutions.feed(formdata)
item.perform(formdata)
assert http_requests.get_last('url') == 'http://remote.example.net/'
assert http_requests.get_last('method') == 'PUT'
payload = json.loads(http_requests.get_last('body'))
assert payload == {'one': 1, 'str': 'abcd', 'evalme': formdata.get_display_id()}
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
item.method = 'PATCH'
item.post = False
item.post_data = {'str': 'abcd', 'one': '=1', 'evalme': '=form_number', 'error': '=1=3'}
pub.substitutions.feed(formdata)
item.perform(formdata)
assert http_requests.get_last('url') == 'http://remote.example.net/'
assert http_requests.get_last('method') == 'PATCH'
payload = json.loads(http_requests.get_last('body'))
assert payload == {'one': 1, 'str': 'abcd', 'evalme': formdata.get_display_id()}
def test_webservice_waitpoint(pub):
item = WebserviceCallStatusItem()
assert item.waitpoint
item.action_on_app_error = ':pass'
item.action_on_4xx = ':pass'
item.action_on_5xx = ':pass'
item.action_on_bad_data = ':pass'
item.action_on_network_errors = ':pass'
assert not item.waitpoint
item.action_on_network_errors = ':stop'
assert item.waitpoint
def test_webservice_call_error_handling(http_requests, 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.action_on_app_error = ':stop'
item.action_on_4xx = ':pass'
item.action_on_5xx = ':pass'
item.action_on_network_errors = ':pass'
with pytest.raises(AbortActionException):
item.perform(formdata)
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/json-errheader1'
item.action_on_app_error = ':stop'
item.action_on_4xx = ':pass'
item.action_on_5xx = ':pass'
item.action_on_network_errors = ':pass'
with pytest.raises(AbortActionException):
item.perform(formdata)
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/json-errheaderstr'
item.action_on_app_error = ':stop'
item.action_on_4xx = ':pass'
item.action_on_5xx = ':pass'
item.action_on_network_errors = ':pass'
with pytest.raises(AbortActionException):
item.perform(formdata)
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/json-err0'
item.varname = 'xxx'
item.perform(formdata)
assert formdata.workflow_data['xxx_status'] == 200
assert formdata.workflow_data['xxx_app_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.varname = 'xxx'
item.action_on_app_error = ':stop'
item.perform(formdata)
assert formdata.workflow_data['xxx_status'] == 200
assert formdata.workflow_data['xxx_app_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.varname = 'xxx'
item.perform(formdata)
assert formdata.workflow_data['xxx_status'] == 200
assert formdata.workflow_data['xxx_app_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-errstr'
item.varname = 'xxx'
item.perform(formdata)
assert formdata.workflow_data['xxx_status'] == 200
assert formdata.workflow_data['xxx_app_error_code'] == 'bug'
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.varname = 'xxx'
item.action_on_app_error = ':stop'
with pytest.raises(AbortActionException):
item.perform(formdata)
assert formdata.workflow_data['xxx_status'] == 200
assert formdata.workflow_data['xxx_app_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.varname = 'xxx'
item.perform(formdata)
assert formdata.workflow_data['xxx_status'] == 200
assert formdata.workflow_data['xxx_app_error_code'] == 0
assert formdata.workflow_data['xxx_app_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.varname = 'xxx'
item.perform(formdata)
assert formdata.workflow_data['xxx_status'] == 200
assert formdata.workflow_data['xxx_app_error_code'] == 1
assert formdata.workflow_data['xxx_app_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-errheaderstr'
item.varname = 'xxx'
item.perform(formdata)
assert formdata.workflow_data['xxx_status'] == 200
assert formdata.workflow_data['xxx_app_error_code'] == 'bug'
assert formdata.workflow_data['xxx_app_error_header'] == 'bug'
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.varname = 'xxx'
item.action_on_app_error = ':stop'
with pytest.raises(AbortActionException):
item.perform(formdata)
assert formdata.workflow_data['xxx_status'] == 200
assert formdata.workflow_data['xxx_app_error_code'] == 1
assert formdata.workflow_data['xxx_app_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.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_app_error_code') == 1
assert formdata.workflow_data.get('xxx_app_error_header') == '1'
assert 'xxx_response' not in formdata.workflow_data
formdata.workflow_data = None
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/xml-errheader'
item.varname = 'xxx'
item.response_type = 'attachment'
item.record_errors = True
item.action_on_app_error = ':stop'
with pytest.raises(AbortActionException):
item.perform(formdata)
assert formdata.workflow_data.get('xxx_status') == 200
assert formdata.workflow_data.get('xxx_app_error_code') == 1
assert formdata.workflow_data.get('xxx_app_error_header') == '1'
assert 'xxx_response' not in formdata.workflow_data
formdata.workflow_data = None
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/xml-errheader'
item.varname = 'xxx'
item.response_type = 'json' # wait for json but receive xml
item.record_errors = True
item.perform(formdata)
assert formdata.workflow_data.get('xxx_status') == 200
assert formdata.workflow_data.get('xxx_app_error_code') == 1
assert formdata.workflow_data.get('xxx_app_error_header') == '1'
assert 'xxx_response' not in formdata.workflow_data
formdata.workflow_data = None
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/json-err1'
item.action_on_app_error = ':stop'
item.response_type = 'attachment' # err value is not an error
item.perform(formdata) # so, everything is "ok" here
formdata.workflow_data = None
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/json-errheaderstr'
item.action_on_app_error = ':stop'
item.response_type = 'attachment'
with pytest.raises(AbortActionException):
item.perform(formdata)
formdata.workflow_data = None
# xml instead of json is not a app_error
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/xml'
item.varname = 'xxx'
item.action_on_app_error = ':stop'
item.action_on_4xx = ':pass'
item.action_on_5xx = ':pass'
item.action_on_network_errors = ':pass'
item.action_on_bad_data = ':pass'
item.perform(formdata)
formdata.workflow_data = None
# connection error
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/connection-error'
item.record_errors = True
item.action_on_network_errors = ':pass'
item.perform(formdata)
assert not formdata.workflow_data
# connection error, with varname
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net/connection-error'
item.varname = 'plop'
item.record_errors = True
item.action_on_network_errors = ':pass'
item.perform(formdata)
assert 'ConnectionError: error' in formdata.evolution[-1].parts[-1].summary
assert formdata.workflow_data['plop_connection_error'].startswith('error')
def test_webservice_call_store_in_backoffice_filefield(http_requests, pub):
wf = Workflow(name='wscall to backoffice file field')
wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(wf)
wf.backoffice_fields_formdef.fields = [
FileField(id='bo1', label='bo field 1', varname='backoffice_file1'),
]
st1 = wf.add_status('Status1')
wf.store()
FormDef.wipe()
formdef = FormDef()
formdef.name = 'baz'
formdef.fields = []
formdef.workflow_id = wf.id
formdef.store()
formdata = formdef.data_class()()
formdata.just_created()
formdata.data = {}
formdata.store()
# check storing response in backoffice file field
item = WebserviceCallStatusItem()
item.parent = st1
item.backoffice_filefield_id = 'bo1'
item.url = 'http://remote.example.net/xml'
item.response_type = 'attachment'
item.record_errors = True
item.perform(formdata)
assert 'bo1' in formdata.data
fbo1 = formdata.data['bo1']
assert fbo1.base_filename == 'file-bo1.xml'
assert fbo1.content_type == 'text/xml'
assert fbo1.get_content().startswith(b'<?xml')
# nothing else is stored
assert formdata.workflow_data is None
assert isinstance(formdata.evolution[-1].parts[0], ContentSnapshotPart)
# store in backoffice file field + varname
formdata = formdef.data_class()()
formdata.data = {}
formdata.just_created()
formdata.store()
item.varname = 'xxx'
item.perform(formdata)
# backoffice file field
assert 'bo1' in formdata.data
fbo1 = formdata.data['bo1']
assert fbo1.base_filename == 'xxx.xml'
assert fbo1.content_type == 'text/xml'
assert fbo1.get_content().startswith(b'<?xml')
# varname => workflow_data and AttachmentEvolutionPart
assert formdata.workflow_data.get('xxx_status') == 200
assert formdata.workflow_data.get('xxx_content_type') == 'text/xml'
attachment = formdata.evolution[-1].parts[-1]
assert isinstance(attachment, AttachmentEvolutionPart)
assert attachment.base_filename == 'xxx.xml'
assert attachment.content_type == 'text/xml'
attachment.fp.seek(0)
assert attachment.fp.read(5) == b'<?xml'
# no more 'bo1' backoffice field: do nothing
formdata = formdef.data_class()()
formdata.data = {}
formdata.just_created()
formdata.store()
wf.backoffice_fields_formdef.fields = [
FileField(id='bo2', label='bo field 2'), # id != 'bo1'
]
item.perform(formdata)
assert formdata.data == {}
# backoffice field is not a field file:
wf.backoffice_fields_formdef.fields = [
StringField(id='bo1', label='bo field 1'),
]
item.perform(formdata)
assert formdata.data == {}
# no field at all:
wf.backoffice_fields_formdef.fields = []
item.perform(formdata)
assert formdata.data == {}
def test_webservice_target_status(pub):
wf = Workflow(name='boo')
status1 = wf.add_status('Status1', 'st1')
status2 = wf.add_status('Status2', 'st2')
wf.store()
item = WebserviceCallStatusItem()
item.parent = status1
assert item.get_target_status() == [status1.id]
item.action_on_app_error = status1.id
item.action_on_4xx = status2.id
item.action_on_5xx = status2.id
targets = item.get_target_status()
assert len(item.get_target_status()) == 4
assert targets.count(status1) == 2
assert targets.count(status2) == 2
item.action_on_bad_data = 'st3' # doesn't exist
targets = item.get_target_status()
assert len(item.get_target_status()) == 4
assert targets.count(status1) == 2
assert targets.count(status2) == 2
def test_webservice_with_complex_data_in_payload(http_requests, pub):
pub.substitutions.feed(MockSubstitutionVariables())
wf = Workflow(name='wf1')
wf.add_status('Status1', 'st1')
wf.add_status('StatusErr', 'sterr')
wf.store()
datasource = {
'type': 'jsonvalue',
'value': json.dumps(
[
{'id': 'a', 'text': 'aa', 'more': 'aaa'},
{'id': 'b', 'text': 'bb', 'more': 'bbb'},
{'id': 'c', 'text': 'cc', 'more': 'ccc'},
]
),
}
FormDef.wipe()
formdef = FormDef()
formdef.name = 'baz'
formdef.fields = [
ItemField(id='1', label='1st field', varname='item', data_source=datasource),
ItemsField(id='2', label='2nd field', varname='items', data_source=datasource),
StringField(id='3', label='3rd field', varname='str'),
StringField(id='4', label='4th field', varname='empty_str'),
StringField(id='5', label='5th field', varname='none'),
BoolField(id='6', label='6th field', varname='bool'),
]
formdef.workflow_id = wf.id
formdef.store()
formdata = formdef.data_class()()
formdata.data = {}
formdata.data['1'] = 'a'
formdata.data['1_display'] = 'aa'
formdata.data['1_structured'] = formdef.fields[0].store_structured_value(formdata.data, '1')
formdata.data['2'] = ['a', 'b']
formdata.data['2_display'] = 'aa, bb'
formdata.data['2_structured'] = formdef.fields[1].store_structured_value(formdata.data, '2')
formdata.data['3'] = 'tutuche'
formdata.data['4'] = 'empty_str'
formdata.data['5'] = None
formdata.data['6'] = False
formdata.just_created()
formdata.store()
attachment_content = b'hello'
formdata.evolution[-1].parts = [
AttachmentEvolutionPart('hello.txt', fp=io.BytesIO(attachment_content), varname='testfile')
]
formdata.store()
item = WebserviceCallStatusItem()
item.method = 'POST'
item.url = 'http://remote.example.net'
item.post_data = {
'item': '{{ form_var_item }}',
'ezt_item': '[form_var_item]',
'items': '{{ form_var_items }}',
'ezt_items': '[form_var_items]',
'item_raw': '{{ form_var_item_raw }}',
'ezt_item_raw': '[form_var_item_raw]',
'items_raw': '{{ form_var_items_raw }}',
'with_items_raw': '{% with x=form_var_items_raw %}{{ x }}{% endwith %}',
'with_items_upper': '{% with x=form_var_items_raw %}{{ x.1|upper }}{% endwith %}',
'ezt_items_raw': '[form_var_items_raw]',
'joined_items_raw': '{{ form_var_items_raw|join:"|" }}',
'forloop_items_raw': '{% for item in form_var_items_raw %}{{item}}|{% endfor %}',
'str': '{{ form_var_str }}',
'str_mod': '{{ form_var_str }}--plop',
'decimal': '{{ "1000"|decimal }}',
'decimal2': '{{ "1000.1"|decimal }}',
'empty_string': '{{ form_var_empty }}',
'none': '{{ form_var_none }}',
'bool': '{{ form_var_bool_raw }}',
'attachment': '{{ form_attachments_testfile }}',
'time': '{{ "13:12"|time }}',
}
pub.substitutions.feed(formdata)
with get_publisher().complex_data():
item.perform(formdata)
assert http_requests.get_last('url') == 'http://remote.example.net/'
assert http_requests.get_last('method') == 'POST'
payload = json.loads(http_requests.get_last('body'))
assert payload == {
'item': 'aa',
'ezt_item': 'aa',
'items': 'aa, bb',
'ezt_items': 'aa, bb',
'item_raw': 'a',
'ezt_item_raw': 'a',
'items_raw': ['a', 'b'],
'with_items_raw': ['a', 'b'],
'with_items_upper': 'B',
'ezt_items_raw': repr(['a', 'b']),
'joined_items_raw': 'a|b',
'forloop_items_raw': 'a|b|',
'str': 'tutuche',
'str_mod': 'tutuche--plop',
'decimal': '1000',
'decimal2': '1000.1',
'empty_string': '',
'none': None,
'bool': False,
'attachment': {
'filename': 'hello.txt',
'content_type': 'application/octet-stream',
'content': base64.b64encode(attachment_content).decode(),
},
'time': '13:12:00',
}
# check an empty boolean field is sent as False
del formdata.data['6']
with get_publisher().complex_data():
item.perform(formdata)
assert http_requests.get_last('url') == 'http://remote.example.net/'
assert http_requests.get_last('method') == 'POST'
payload = json.loads(http_requests.get_last('body'))
assert payload['bool'] is False
def test_webservice_with_complex_data_in_query_string(http_requests, pub):
pub.substitutions.feed(MockSubstitutionVariables())
wf = Workflow(name='wf1')
wf.add_status('Status1', 'st1')
wf.add_status('StatusErr', 'sterr')
wf.store()
datasource = {
'type': 'jsonvalue',
'value': json.dumps(
[
{'id': 'a', 'text': 'aa', 'more': 'aaa'},
{'id': 'b', 'text': 'bb', 'more': 'bbb'},
{'id': 'c', 'text': 'cc', 'more': 'ccc'},
]
),
}
BlockDef.wipe()
block = BlockDef()
block.name = 'foobar'
block.fields = [StringField(id='1', label='String', varname='string')]
block.store()
FormDef.wipe()
formdef = FormDef()
formdef.name = 'baz'
formdef.fields = [
ItemField(id='1', label='1st field', varname='item', data_source=datasource),
ItemsField(id='2', label='2nd field', varname='items', data_source=datasource),
StringField(id='3', label='3rd field', varname='str'),
StringField(id='4', label='4th field', varname='empty_str'),
StringField(id='5', label='5th field', varname='none'),
BoolField(id='6', label='6th field', varname='bool'),
BlockField(id='7', label='7th field', varname='block', block_slug=block.slug, max_items=3),
]
formdef.workflow_id = wf.id
formdef.store()
formdata = formdef.data_class()()
formdata.data = {}
formdata.data['1'] = 'a'
formdata.data['1_display'] = 'aa'
formdata.data['1_structured'] = formdef.fields[0].store_structured_value(formdata.data, '1')
formdata.data['2'] = ['a', 'b']
formdata.data['2_display'] = 'aa, bb'
formdata.data['2_structured'] = formdef.fields[1].store_structured_value(formdata.data, '2')
formdata.data['3'] = 'tutuche'
formdata.data['4'] = 'empty_str'
formdata.data['5'] = None
formdata.data['6'] = False
formdata.data['7'] = {
'data': [
{
'1': 'plop',
},
{
'1': 'poulpe',
},
],
'schema': {},
}
formdata.just_created()
formdata.store()
item = WebserviceCallStatusItem()
item.method = 'POST'
item.url = 'http://remote.example.net'
item.qs_data = {
'item': '{{ form_var_item }}',
'items': '{{ form_var_items }}',
'item_raw': '{{ form_var_item_raw }}',
'items_raw': '{{ form_var_items_raw }}',
'with_items_raw': '{% with x=form_var_items_raw %}{{ x }}{% endwith %}',
'with_items_upper': '{% with x=form_var_items_raw %}{{ x.1|upper }}{% endwith %}',
'joined_items_raw': '{{ form_var_items_raw|join:"|" }}',
'forloop_items_raw': '{% for item in form_var_items_raw %}{{item}}|{% endfor %}',
'str': '{{ form_var_str }}',
'str_mod': '{{ form_var_str }}--plop',
'int': '{{ 1000 }}',
'decimal': '{{ "1000"|decimal }}',
'decimal2': '{{ "1000.1"|decimal }}',
'empty_string': '{{ form_var_empty }}',
'none': '{{ form_var_none }}',
'bool': '{{ form_var_bool_raw }}',
'time': '{{ "13:12"|time }}',
'block_template': '{% for b in form_var_block %}{{ b.string }}{% endfor %}',
}
pub.substitutions.feed(formdata)
item.perform(formdata)
assert sorted(urllib.parse.parse_qsl(urllib.parse.urlparse(http_requests.get_last('url')).query)) == [
('block_template', 'ploppoulpe'),
('bool', 'False'),
('decimal', '1E+3'),
('decimal2', '1000.1'),
('forloop_items_raw', 'a|b|'),
('int', '1000'),
('item', 'aa'),
('item_raw', 'a'),
('items', 'aa, bb'),
('items_raw', 'a'),
('items_raw', 'b'),
('joined_items_raw', 'a|b'),
('str', 'tutuche'),
('str_mod', 'tutuche--plop'),
('time', '13:12:00'),
('with_items_raw', 'a'),
('with_items_raw', 'b'),
('with_items_upper', 'B'),
]