632 lines
18 KiB
Python
632 lines
18 KiB
Python
import datetime
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
from pyquery import PyQuery
|
|
from quixote import cleanup
|
|
|
|
from wcs.fields import DateField, StringField
|
|
from wcs.formdef import FormDef
|
|
from wcs.qommon.http_request import HTTPRequest
|
|
from wcs.wf.jump import JumpWorkflowStatusItem, _apply_timeouts
|
|
from wcs.workflows import Workflow, perform_items
|
|
|
|
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
|
|
from .test_all import admin_user # noqa pylint: disable=unused-import
|
|
|
|
|
|
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.cfg['identification'] = {'methods': ['password']}
|
|
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 rewind(formdata, seconds):
|
|
# utility function to move formdata back in time
|
|
formdata.receipt_time = formdata.receipt_time - datetime.timedelta(seconds=seconds)
|
|
formdata.evolution[-1].time = formdata.evolution[-1].time - datetime.timedelta(seconds=seconds)
|
|
|
|
|
|
def test_jump_nothing(pub):
|
|
FormDef.wipe()
|
|
formdef = FormDef()
|
|
formdef.name = 'foobar'
|
|
formdef.store()
|
|
formdata = formdef.data_class()()
|
|
item = JumpWorkflowStatusItem()
|
|
assert item.check_condition(formdata) is True
|
|
|
|
|
|
def test_jump_datetime_condition(pub):
|
|
FormDef.wipe()
|
|
formdef = FormDef()
|
|
formdef.name = 'foobar'
|
|
formdef.store()
|
|
formdata = formdef.data_class()()
|
|
item = JumpWorkflowStatusItem()
|
|
yesterday = datetime.datetime.now() - datetime.timedelta(days=1)
|
|
item.condition = {
|
|
'type': 'python',
|
|
'value': 'datetime.datetime.now() > datetime.datetime(%s, %s, %s)' % yesterday.timetuple()[:3],
|
|
}
|
|
assert item.check_condition(formdata) is True
|
|
|
|
tomorrow = datetime.datetime.now() + datetime.timedelta(days=1)
|
|
item.condition = {
|
|
'type': 'python',
|
|
'value': 'datetime.datetime.now() > datetime.datetime(%s, %s, %s)' % tomorrow.timetuple()[:3],
|
|
}
|
|
assert item.check_condition(formdata) is False
|
|
|
|
|
|
def test_jump_date_conditions(pub):
|
|
FormDef.wipe()
|
|
formdef = FormDef()
|
|
formdef.name = 'foobar'
|
|
formdef.fields = [DateField(id='2', label='Date', varname='date')]
|
|
formdef.store()
|
|
|
|
# create/store/get, to make sure the date format is acceptable
|
|
formdata = formdef.data_class()()
|
|
formdata.data = {'2': DateField().convert_value_from_str('2015-01-04')}
|
|
formdata.store()
|
|
formdata = formdef.data_class().get(formdata.id)
|
|
|
|
pub.substitutions.feed(formdata)
|
|
|
|
item = JumpWorkflowStatusItem()
|
|
item.condition = {
|
|
'type': 'python',
|
|
'value': 'utils.make_date(form_var_date) == utils.make_date("2015-01-04")',
|
|
}
|
|
assert item.check_condition(formdata) is True
|
|
|
|
item = JumpWorkflowStatusItem()
|
|
item.condition = {'type': 'python', 'value': 'utils.time_delta(form_var_date, "2015-01-04").days == 0'}
|
|
assert item.check_condition(formdata) is True
|
|
|
|
item = JumpWorkflowStatusItem()
|
|
item.condition = {'type': 'python', 'value': 'utils.time_delta(utils.today(), "2015-01-04").days > 0'}
|
|
assert item.check_condition(formdata) is True
|
|
|
|
item = JumpWorkflowStatusItem()
|
|
item.condition = {
|
|
'type': 'python',
|
|
'value': 'utils.time_delta(datetime.datetime.now(), "2015-01-04").days > 0',
|
|
}
|
|
assert item.check_condition(formdata) is True
|
|
|
|
item = JumpWorkflowStatusItem()
|
|
item.condition = {
|
|
'type': 'python',
|
|
'value': 'utils.time_delta(utils.time.localtime(), "2015-01-04").days > 0',
|
|
}
|
|
assert item.check_condition(formdata) is True
|
|
|
|
|
|
def test_jump_count_condition(pub):
|
|
FormDef.wipe()
|
|
formdef = FormDef()
|
|
formdef.name = 'foobar'
|
|
formdef.store()
|
|
pub.substitutions.feed(formdef)
|
|
formdef.data_class().wipe()
|
|
formdata = formdef.data_class()()
|
|
item = JumpWorkflowStatusItem()
|
|
item.condition = {'type': 'python', 'value': 'form_objects.count < 2'}
|
|
assert item.check_condition(formdata) is True
|
|
|
|
for _ in range(10):
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.store()
|
|
|
|
item.condition = {'type': 'python', 'value': 'form_objects.count < 2'}
|
|
assert item.check_condition(formdata) is False
|
|
|
|
|
|
def test_jump_bad_python_condition(pub):
|
|
FormDef.wipe()
|
|
formdef = FormDef()
|
|
formdef.name = 'foobar'
|
|
formdef.store()
|
|
pub.substitutions.feed(formdef)
|
|
formdef.data_class().wipe()
|
|
formdata = formdef.data_class()()
|
|
item = JumpWorkflowStatusItem()
|
|
|
|
item.condition = {'type': 'python', 'value': 'form_var_foobar == 0'}
|
|
assert item.check_condition(formdata) is False
|
|
assert pub.loggederror_class.count() == 1
|
|
logged_error = pub.loggederror_class.select()[0]
|
|
assert logged_error.summary == 'Failed to evaluate condition'
|
|
assert logged_error.exception_class == 'NameError'
|
|
assert logged_error.exception_message == "name 'form_var_foobar' is not defined"
|
|
assert logged_error.context == {
|
|
'stack': [
|
|
{
|
|
'condition': 'form_var_foobar == 0',
|
|
'condition_type': 'python',
|
|
'source_label': 'Automatic Jump',
|
|
'source_url': '',
|
|
}
|
|
]
|
|
}
|
|
|
|
pub.loggederror_class.wipe()
|
|
item.condition = {'type': 'python', 'value': '~ invalid ~'}
|
|
assert item.check_condition(formdata) is False
|
|
assert pub.loggederror_class.count() == 1
|
|
logged_error = pub.loggederror_class.select()[0]
|
|
assert logged_error.summary == 'Failed to evaluate condition'
|
|
assert logged_error.exception_class == 'SyntaxError'
|
|
assert logged_error.exception_message == 'invalid syntax (<string>, line 1)'
|
|
assert logged_error.context == {
|
|
'stack': [
|
|
{
|
|
'condition': '~ invalid ~',
|
|
'source_url': '',
|
|
'source_label': 'Automatic Jump',
|
|
'condition_type': 'python',
|
|
}
|
|
]
|
|
}
|
|
|
|
|
|
def test_jump_django_conditions(pub):
|
|
FormDef.wipe()
|
|
formdef = FormDef()
|
|
formdef.name = 'foobar'
|
|
formdef.fields = [
|
|
StringField(id='1', label='Test', varname='foo'),
|
|
]
|
|
formdef.store()
|
|
formdata = formdef.data_class()()
|
|
formdata.data = {'1': 'hello'}
|
|
pub.substitutions.feed(formdata)
|
|
item = JumpWorkflowStatusItem()
|
|
|
|
item.condition = {'type': 'django', 'value': '1 < 2'}
|
|
assert item.check_condition(formdata) is True
|
|
|
|
item.condition = {'type': 'django', 'value': 'form_var_foo == "hello"'}
|
|
assert item.check_condition(formdata) is True
|
|
|
|
item.condition = {'type': 'django', 'value': 'form_var_foo|first|upper == "H"'}
|
|
assert item.check_condition(formdata) is True
|
|
|
|
item.condition = {'type': 'django', 'value': 'form_var_foo|first|upper == "X"'}
|
|
assert item.check_condition(formdata) is False
|
|
|
|
assert pub.loggederror_class.count() == 0
|
|
|
|
item.condition = {'type': 'django', 'value': '~ invalid ~'}
|
|
assert item.check_condition(formdata) is False
|
|
assert pub.loggederror_class.count() == 1
|
|
logged_error = pub.loggederror_class.select()[0]
|
|
assert logged_error.summary == 'Failed to evaluate condition'
|
|
assert logged_error.exception_class == 'TemplateSyntaxError'
|
|
assert logged_error.exception_message == "Could not parse the remainder: '~' from '~'"
|
|
assert logged_error.context == {
|
|
'stack': [
|
|
{
|
|
'condition': '~ invalid ~',
|
|
'source_url': '',
|
|
'source_label': 'Automatic Jump',
|
|
'condition_type': 'django',
|
|
}
|
|
]
|
|
}
|
|
|
|
|
|
def test_timeout(pub):
|
|
workflow = Workflow(name='timeout')
|
|
st1 = workflow.add_status('Status1', 'st1')
|
|
workflow.add_status('Status2', 'st2')
|
|
|
|
jump = st1.add_action('jump', id='_jump')
|
|
jump.by = ['_submitter', '_receiver']
|
|
jump.timeout = 30 * 60 # 30 minutes
|
|
jump.status = 'st2'
|
|
|
|
workflow.store()
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'baz'
|
|
formdef.fields = []
|
|
formdef.workflow_id = workflow.id
|
|
assert formdef.get_workflow().id == workflow.id
|
|
formdef.store()
|
|
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
rewind(formdata, seconds=40 * 60)
|
|
formdata.store()
|
|
formdata_id = formdata.id
|
|
|
|
_apply_timeouts(pub)
|
|
|
|
assert formdef.data_class().get(formdata_id).status == 'wf-st2'
|
|
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.store()
|
|
formdata_id = formdata.id
|
|
with mock.patch('wcs.wf.jump.JumpWorkflowStatusItem.check_condition') as must_jump:
|
|
must_jump.return_value = False
|
|
_apply_timeouts(pub)
|
|
assert must_jump.call_count == 0 # not enough time has passed
|
|
|
|
# check a lower than minimal delay is not considered
|
|
jump.timeout = 5 * 50 # 5 minutes
|
|
workflow.store()
|
|
rewind(formdata, seconds=10 * 60)
|
|
formdata.store()
|
|
_apply_timeouts(pub)
|
|
assert must_jump.call_count == 0
|
|
|
|
# but is executed once delay is reached
|
|
rewind(formdata, seconds=10 * 60)
|
|
formdata.store()
|
|
_apply_timeouts(pub)
|
|
assert must_jump.call_count == 1
|
|
|
|
# check a templated timeout is considered as minimal delay for explicit evaluation
|
|
jump.timeout = '{{ "0" }}'
|
|
workflow.store()
|
|
_apply_timeouts(pub)
|
|
assert must_jump.call_count == 2
|
|
|
|
# check there's no crash on workflow without jumps
|
|
formdef = FormDef()
|
|
formdef.name = 'xxx'
|
|
formdef.store()
|
|
_apply_timeouts(pub)
|
|
|
|
|
|
def test_timeout_with_humantime_template(pub):
|
|
workflow = Workflow(name='timeout')
|
|
st1 = workflow.add_status('Status1', 'st1')
|
|
workflow.add_status('Status2', 'st2')
|
|
|
|
jump = st1.add_action('jump', id='_jump')
|
|
jump.by = ['_submitter', '_receiver']
|
|
jump.timeout = '{{ 30 }} minutes'
|
|
jump.status = 'st2'
|
|
|
|
workflow.store()
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'baz'
|
|
formdef.fields = []
|
|
formdef.workflow_id = workflow.id
|
|
assert formdef.get_workflow().id == workflow.id
|
|
formdef.store()
|
|
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.store()
|
|
formdata_id = formdata.id
|
|
|
|
_apply_timeouts(pub)
|
|
assert formdef.data_class().get(formdata_id).status == 'wf-st1' # no change
|
|
|
|
rewind(formdata, seconds=40 * 60)
|
|
formdata.store()
|
|
_apply_timeouts(pub)
|
|
assert formdef.data_class().get(formdata_id).status == 'wf-st2'
|
|
|
|
# invalid timeout value
|
|
jump.timeout = '{{ 30 }} plop'
|
|
workflow.store()
|
|
formdef.refresh_from_storage()
|
|
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.store()
|
|
formdata_id = formdata.id
|
|
|
|
pub.loggederror_class.wipe()
|
|
|
|
rewind(formdata, seconds=40 * 60)
|
|
formdata.store()
|
|
_apply_timeouts(pub)
|
|
assert formdef.data_class().get(formdata_id).status == 'wf-st1' # no change
|
|
|
|
assert pub.loggederror_class.count() == 1
|
|
logged_error = pub.loggederror_class.select()[0]
|
|
assert logged_error.summary == "Error in timeout value '30 plop' (computed from '{{ 30 }} plop')"
|
|
|
|
# template timeout value returning nothing
|
|
jump.timeout = '{% if 1 %}{% endif %}'
|
|
workflow.store()
|
|
formdef.refresh_from_storage()
|
|
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.store()
|
|
formdata_id = formdata.id
|
|
|
|
pub.loggederror_class.wipe()
|
|
|
|
rewind(formdata, seconds=40 * 60)
|
|
formdata.store()
|
|
_apply_timeouts(pub)
|
|
assert formdef.data_class().get(formdata_id).status == 'wf-st1' # no change
|
|
|
|
assert pub.loggederror_class.count() == 1
|
|
logged_error = pub.loggederror_class.select()[0]
|
|
assert logged_error.summary == "Error in timeout value '' (computed from '{% if 1 %}{% endif %}')"
|
|
|
|
|
|
def test_legacy_timeout(pub):
|
|
workflow = Workflow(name='timeout')
|
|
st1 = workflow.add_status('Status1', 'st1')
|
|
workflow.add_status('Status2', 'st2')
|
|
|
|
jump = st1.add_action('timeout', id='_jump')
|
|
jump.timeout = 30 * 60 # 30 minutes
|
|
jump.status = 'st2'
|
|
|
|
workflow.store()
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'baz'
|
|
formdef.fields = []
|
|
formdef.workflow_id = workflow.id
|
|
assert formdef.get_workflow().id == workflow.id
|
|
formdef.store()
|
|
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
rewind(formdata, seconds=40 * 60)
|
|
formdata.store()
|
|
formdata_id = formdata.id
|
|
|
|
_apply_timeouts(pub)
|
|
|
|
assert formdef.data_class().get(formdata_id).status == 'wf-st2'
|
|
|
|
|
|
def test_timeout_then_remove(pub):
|
|
workflow = Workflow(name='timeout-then-remove')
|
|
st1 = workflow.add_status('Status1', 'st1')
|
|
st2 = workflow.add_status('Status2', 'st2')
|
|
|
|
jump = st1.add_action('jump', id='_jump')
|
|
jump.by = ['_submitter', '_receiver']
|
|
jump.timeout = 30 * 60 # 30 minutes
|
|
jump.status = 'st2'
|
|
|
|
st2.add_action('remove')
|
|
|
|
workflow.store()
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'baz%s' % id(pub)
|
|
formdef.fields = []
|
|
formdef.workflow_id = workflow.id
|
|
assert formdef.get_workflow().id == workflow.id
|
|
formdef.store()
|
|
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
rewind(formdata, seconds=40 * 60)
|
|
formdata.store()
|
|
formdata.record_workflow_event('frontoffice-created')
|
|
formdata_id = formdata.id
|
|
|
|
assert str(formdata_id) in [str(x) for x in formdef.data_class().keys()]
|
|
assert bool(formdata.get_workflow_traces())
|
|
|
|
_apply_timeouts(pub)
|
|
|
|
assert not str(formdata_id) in [str(x) for x in formdef.data_class().keys()]
|
|
# check workflow traces are removed
|
|
assert not bool(formdata.get_workflow_traces())
|
|
|
|
|
|
def test_timeout_with_mark(pub):
|
|
workflow = Workflow(name='timeout')
|
|
st1 = workflow.add_status('Status1', 'st1')
|
|
workflow.add_status('Status2', 'st2')
|
|
|
|
jump = st1.add_action('jump', id='_jump')
|
|
jump.by = ['_submitter', '_receiver']
|
|
jump.timeout = 30 * 60 # 30 minutes
|
|
jump.status = 'st2'
|
|
jump.set_marker_on_status = True
|
|
|
|
workflow.store()
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'baz'
|
|
formdef.fields = []
|
|
formdef.workflow_id = workflow.id
|
|
assert formdef.get_workflow().id == workflow.id
|
|
formdef.store()
|
|
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
rewind(formdata, seconds=40 * 60)
|
|
formdata.store()
|
|
formdata_id = formdata.id
|
|
|
|
_apply_timeouts(pub)
|
|
|
|
formdata = formdef.data_class().get(formdata_id)
|
|
assert formdata.workflow_data.get('_markers_stack') == [{'status_id': 'st1'}]
|
|
|
|
|
|
def test_timeout_on_anonymised(pub):
|
|
workflow = Workflow(name='timeout')
|
|
st1 = workflow.add_status('Status1', 'st1')
|
|
workflow.add_status('Status2', 'st2')
|
|
|
|
jump = st1.add_action('timeout', id='_jump')
|
|
jump.timeout = 30 * 60 # 30 minutes
|
|
jump.status = 'st2'
|
|
|
|
workflow.store()
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'baz'
|
|
formdef.fields = []
|
|
formdef.workflow_id = workflow.id
|
|
assert formdef.get_workflow().id == workflow.id
|
|
formdef.store()
|
|
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
rewind(formdata, seconds=40 * 60)
|
|
formdata.anonymise()
|
|
formdata.store()
|
|
formdata_id = formdata.id
|
|
|
|
_apply_timeouts(pub)
|
|
|
|
assert formdef.data_class().get(formdata_id).status == 'wf-st1' # no change
|
|
|
|
|
|
def test_jump_missing_previous_mark(pub):
|
|
FormDef.wipe()
|
|
Workflow.wipe()
|
|
|
|
workflow = Workflow(name='jump-mark')
|
|
st1 = workflow.add_status('Status1', 'st1')
|
|
|
|
jump = st1.add_action('jump', id='_jump')
|
|
jump.by = ['_submitter', '_receiver']
|
|
jump.status = '_previous'
|
|
jump.timeout = 30 * 60 # 30 minutes
|
|
|
|
workflow.store()
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'baz'
|
|
formdef.fields = []
|
|
formdef.workflow_id = workflow.id
|
|
formdef.store()
|
|
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
rewind(formdata, seconds=40 * 60)
|
|
formdata.store()
|
|
|
|
pub.loggederror_class.wipe()
|
|
_apply_timeouts(pub)
|
|
assert pub.loggederror_class.count() == 1
|
|
|
|
|
|
def test_conditional_jump_vs_tracing(pub):
|
|
workflow = Workflow(name='wf')
|
|
st1 = workflow.add_status('Status1', 'st1')
|
|
workflow.add_status('Status2', 'st2')
|
|
comment = st1.add_action('register-comment')
|
|
comment.comment = 'hello world'
|
|
jump1 = st1.add_action('jump')
|
|
jump1.parent = st1
|
|
jump1.condition = {'type': 'django', 'value': 'False'}
|
|
jump1.status = 'wf-st2'
|
|
jump2 = st1.add_action('jump')
|
|
jump2.parent = st1
|
|
jump2.status = 'wf-st2'
|
|
workflow.store()
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'baz'
|
|
formdef.workflow = workflow
|
|
formdef.store()
|
|
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.store()
|
|
perform_items(st1.items, formdata)
|
|
formdata.refresh_from_storage()
|
|
assert [(x.action_item_key, x.action_item_id) for x in formdata.get_workflow_traces()][-2:] == [
|
|
('register-comment', str(comment.id)),
|
|
('jump', str(jump2.id)),
|
|
]
|
|
|
|
|
|
def test_timeout_tracing(pub, admin_user):
|
|
workflow = Workflow(name='timeout')
|
|
st1 = workflow.add_status('Status1', 'st1')
|
|
st2 = workflow.add_status('Status2', 'st2')
|
|
|
|
jump = st1.add_action('timeout', id='_jump')
|
|
jump.timeout = 30 * 60 # 30 minutes
|
|
jump.status = 'st2'
|
|
|
|
add_message = st2.add_action('register-comment')
|
|
add_message.comment = 'hello'
|
|
workflow.store()
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'baz'
|
|
formdef.fields = []
|
|
formdef.workflow_id = workflow.id
|
|
assert formdef.get_workflow().id == workflow.id
|
|
formdef.store()
|
|
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
rewind(formdata, seconds=40 * 60)
|
|
formdata.store()
|
|
formdata.record_workflow_event('backoffice-created')
|
|
_apply_timeouts(pub)
|
|
|
|
resp = login(get_app(pub), username='admin', password='admin').get(
|
|
formdata.get_backoffice_url() + 'inspect'
|
|
)
|
|
assert [PyQuery(x).text() for x in resp.pyquery('#inspect-timeline li > *:nth-child(2)')] == [
|
|
'Created (backoffice submission)',
|
|
'Status1',
|
|
'Timeout jump - Change Status on Timeout',
|
|
'Status2',
|
|
'History Message',
|
|
]
|
|
|
|
|
|
def test_jump_self_timeout(pub):
|
|
FormDef.wipe()
|
|
Workflow.wipe()
|
|
|
|
workflow = Workflow(name='timeout')
|
|
st1 = workflow.add_status('Status1', 'st1')
|
|
|
|
jump = st1.add_action('jump')
|
|
jump.timeout = 30 * 60 # 30 minutes
|
|
jump.status = 'st1'
|
|
|
|
workflow.store()
|
|
|
|
formdef = FormDef()
|
|
formdef.name = 'baz'
|
|
formdef.fields = []
|
|
formdef.workflow_id = workflow.id
|
|
formdef.store()
|
|
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
rewind(formdata, seconds=40 * 60)
|
|
formdata.store()
|
|
formdata.record_workflow_event('backoffice-created')
|
|
_apply_timeouts(pub)
|