567 lines
18 KiB
Python
567 lines
18 KiB
Python
import base64
|
|
import os
|
|
|
|
import pytest
|
|
from quixote import get_publisher
|
|
|
|
from wcs import fields
|
|
from wcs.api_access import ApiAccess
|
|
from wcs.formdef import FormDef
|
|
from wcs.qommon.http_request import HTTPRequest
|
|
from wcs.qommon.ident.password_accounts import PasswordAccount
|
|
from wcs.wf.register_comment import JournalEvolutionPart
|
|
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef
|
|
|
|
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
|
|
from .utils import sign_uri
|
|
|
|
|
|
@pytest.fixture
|
|
def pub(emails):
|
|
pub = create_temporary_pub()
|
|
|
|
req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
|
|
pub.set_app_dir(req)
|
|
pub.cfg['identification'] = {'methods': ['password']}
|
|
pub.cfg['language'] = {'language': 'en'}
|
|
pub.write_cfg()
|
|
|
|
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
|
fd.write(
|
|
'''\
|
|
[api-secrets]
|
|
coucou = 1234
|
|
'''
|
|
)
|
|
|
|
return pub
|
|
|
|
|
|
def teardown_module(module):
|
|
clean_temporary_pub()
|
|
|
|
|
|
@pytest.fixture
|
|
def local_user():
|
|
get_publisher().user_class.wipe()
|
|
user = get_publisher().user_class()
|
|
user.name = 'Jean Darmette'
|
|
user.email = 'jean.darmette@triffouilis.fr'
|
|
user.name_identifiers = ['0123456789']
|
|
user.store()
|
|
return user
|
|
|
|
|
|
@pytest.fixture
|
|
def admin_user():
|
|
get_publisher().user_class.wipe()
|
|
user = get_publisher().user_class()
|
|
user.name = 'John Doe Admin'
|
|
user.email = 'john.doe@example.com'
|
|
user.name_identifiers = ['0123456789']
|
|
user.is_admin = True
|
|
user.store()
|
|
|
|
account = PasswordAccount(id='admin')
|
|
account.set_password('admin')
|
|
account.user_id = user.id
|
|
account.store()
|
|
|
|
return user
|
|
|
|
|
|
def test_workflow_trigger(pub, local_user):
|
|
workflow = Workflow(name='test')
|
|
st1 = workflow.add_status('Status1', 'st1')
|
|
jump = st1.add_action('jump')
|
|
jump.trigger = 'XXX'
|
|
jump.status = 'st2'
|
|
workflow.add_status('Status2', 'st2')
|
|
workflow.store()
|
|
|
|
FormDef.wipe()
|
|
formdef = FormDef()
|
|
formdef.name = 'test'
|
|
formdef.fields = []
|
|
formdef.workflow_id = workflow.id
|
|
formdef.store()
|
|
|
|
formdef.data_class().wipe()
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.store()
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st1'
|
|
|
|
# incomplete URL
|
|
get_app(pub).post(sign_uri(formdata.get_url() + 'jump/'), status=404)
|
|
get_app(pub).post(sign_uri(formdata.get_url() + 'jump'), status=404)
|
|
get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/'), status=404)
|
|
get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger'), status=404)
|
|
|
|
get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=200)
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st2'
|
|
assert formdef.data_class().get(formdata.id).evolution[-1].who is None
|
|
|
|
# check with trailing slash
|
|
formdata.store() # reset
|
|
get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX/'), status=200)
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st2'
|
|
|
|
# verify trigger presence (not-404 response)
|
|
formdata.store() # reset
|
|
get_app(pub).get(sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=403) # not 404: ok
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st1'
|
|
get_app(pub).get(sign_uri(formdata.get_url() + 'jump/trigger/ABC'), status=404)
|
|
# jump, and then test trigger is not available
|
|
get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=200)
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st2'
|
|
get_app(pub).get(sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=404)
|
|
|
|
pub.role_class.wipe()
|
|
role = pub.role_class(name='xxx')
|
|
role.store()
|
|
|
|
jump.by = [role.id]
|
|
workflow.store()
|
|
|
|
formdata.store() # (will get back to wf-st1)
|
|
get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=403)
|
|
|
|
get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX', user=local_user), status=403)
|
|
|
|
local_user.roles = [role.id]
|
|
local_user.store()
|
|
get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX', user=local_user), status=200)
|
|
|
|
|
|
def test_workflow_trigger_with_data(pub, local_user):
|
|
workflow = Workflow(name='test')
|
|
st1 = workflow.add_status('Status1', 'st1')
|
|
jump = st1.add_action('jump')
|
|
jump.trigger = 'XXX'
|
|
jump.status = 'st2'
|
|
workflow.add_status('Status2', 'st2')
|
|
workflow.store()
|
|
|
|
FormDef.wipe()
|
|
formdef = FormDef()
|
|
formdef.name = 'test'
|
|
formdef.fields = []
|
|
formdef.workflow_id = workflow.id
|
|
formdef.store()
|
|
|
|
formdef.data_class().wipe()
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.store()
|
|
|
|
get_app(pub).post_json(
|
|
sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=200, params={'test': 'data'}
|
|
)
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st2'
|
|
assert formdef.data_class().get(formdata.id).workflow_data == {'test': 'data'}
|
|
|
|
# post with empty dictionary
|
|
formdata.store() # reset
|
|
get_app(pub).post_json(sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=200, params={})
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st2'
|
|
assert not formdef.data_class().get(formdata.id).workflow_data
|
|
|
|
# post with empty data
|
|
formdata.store() # reset
|
|
get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=200)
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st2'
|
|
assert not formdef.data_class().get(formdata.id).workflow_data
|
|
|
|
# post with empty data, but declare json content-type
|
|
formdata.store() # reset
|
|
get_app(pub).post(
|
|
sign_uri(formdata.get_url() + 'jump/trigger/XXX'),
|
|
status=200,
|
|
headers={'content-type': 'application/json'},
|
|
)
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st2'
|
|
assert not formdef.data_class().get(formdata.id).workflow_data
|
|
|
|
# post with invalid JSON data
|
|
formdata.store() # reset
|
|
get_app(pub).post(
|
|
sign_uri(formdata.get_url() + 'jump/trigger/XXX'),
|
|
status=400,
|
|
headers={'content-type': 'application/json'},
|
|
params='ERROR',
|
|
)
|
|
|
|
|
|
def test_workflow_trigger_with_file_data(pub, local_user):
|
|
workflow = Workflow(name='test')
|
|
|
|
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
|
|
workflow.backoffice_fields_formdef.fields = [
|
|
fields.FileField(id='bo1', label='bo field 1', type='file'),
|
|
]
|
|
st1 = workflow.add_status('Status1', 'st1')
|
|
jump = st1.add_action('jump')
|
|
jump.trigger = 'XXX'
|
|
jump.status = 'st2'
|
|
|
|
st2 = workflow.add_status('Status2', 'st2')
|
|
setbo = st2.add_action('set-backoffice-fields')
|
|
setbo.fields = [{'field_id': 'bo1', 'value': '{{ form_workflow_data_document }}'}]
|
|
|
|
workflow.store()
|
|
|
|
FormDef.wipe()
|
|
formdef = FormDef()
|
|
formdef.name = 'test'
|
|
formdef.fields = []
|
|
formdef.workflow_id = workflow.id
|
|
formdef.store()
|
|
|
|
formdef.data_class().wipe()
|
|
formdata = formdef.data_class()()
|
|
formdata.data = {}
|
|
formdata.just_created()
|
|
formdata.store()
|
|
|
|
params = {
|
|
'document': {
|
|
'filename': 'test.pdf',
|
|
'b64_content': base64.encodebytes(b'%PDF-1.4 ...').decode(),
|
|
'content_type': 'application/pdf',
|
|
}
|
|
}
|
|
|
|
get_app(pub).post_json(
|
|
sign_uri(formdata.get_url() + 'jump/trigger/XXX'),
|
|
status=200,
|
|
params=params,
|
|
headers={'content-type': 'application/json'},
|
|
)
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st2'
|
|
assert formdef.data_class().get(formdata.id).workflow_data == params
|
|
|
|
|
|
def test_workflow_trigger_with_condition(pub, local_user):
|
|
workflow = Workflow(name='test')
|
|
st1 = workflow.add_status('Status1', 'st1')
|
|
jump = st1.add_action('jump')
|
|
jump.trigger = 'XXX'
|
|
jump.condition = {'type': 'django', 'value': 'form_var_foo == "bar"'}
|
|
jump.status = 'st2'
|
|
workflow.add_status('Status2', 'st2')
|
|
workflow.store()
|
|
|
|
FormDef.wipe()
|
|
formdef = FormDef()
|
|
formdef.name = 'test'
|
|
formdef.fields = [fields.StringField(id='0', label='foo', varname='foo')]
|
|
formdef.workflow_id = workflow.id
|
|
formdef.store()
|
|
|
|
formdef.data_class().wipe()
|
|
formdata = formdef.data_class()()
|
|
formdata.data = {'0': 'foo'}
|
|
formdata.just_created()
|
|
formdata.store()
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st1'
|
|
|
|
resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=403)
|
|
assert resp.json == {'err_desc': 'unmet condition', 'err': 1}
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st1'
|
|
# check without json
|
|
resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX', format=None), status=403)
|
|
assert resp.content_type == 'text/html'
|
|
|
|
formdata.data['0'] = 'bar'
|
|
formdata.store()
|
|
resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'))
|
|
assert resp.json == {'err': 0, 'url': None}
|
|
|
|
|
|
def test_workflow_trigger_jump_once(pub, local_user):
|
|
workflow = Workflow(name='test')
|
|
st1 = workflow.add_status('Status1', 'st1')
|
|
st2 = workflow.add_status('Status2', 'st2')
|
|
workflow.add_status('Status3', 'st3')
|
|
jump = st1.add_action('jump')
|
|
jump.trigger = 'XXX'
|
|
jump.status = 'st2'
|
|
jump = st2.add_action('jump')
|
|
jump.trigger = 'XXX'
|
|
jump.status = 'st3'
|
|
workflow.store()
|
|
|
|
FormDef.wipe()
|
|
formdef = FormDef()
|
|
formdef.name = 'test'
|
|
formdef.fields = []
|
|
formdef.workflow_id = workflow.id
|
|
formdef.store()
|
|
|
|
formdef.data_class().wipe()
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.store()
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st1'
|
|
|
|
resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'))
|
|
assert resp.json == {'err': 0, 'url': None}
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st2'
|
|
|
|
resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'))
|
|
assert resp.json == {'err': 0, 'url': None}
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st3'
|
|
|
|
|
|
def test_workflow_trigger_api_access(pub, local_user):
|
|
ApiAccess.wipe()
|
|
pub.role_class.wipe()
|
|
role = pub.role_class(name='xxx')
|
|
role.store()
|
|
role2 = pub.role_class(name='xxx2')
|
|
role2.store()
|
|
|
|
workflow = Workflow(name='test')
|
|
st1 = workflow.add_status('Status1', 'st1')
|
|
jump = st1.add_action('jump')
|
|
jump.trigger = 'XXX'
|
|
jump.status = 'st2'
|
|
workflow.add_status('Status2', 'st2')
|
|
workflow.store()
|
|
|
|
FormDef.wipe()
|
|
formdef = FormDef()
|
|
formdef.name = 'test'
|
|
formdef.fields = []
|
|
formdef.workflow_id = workflow.id
|
|
formdef.store()
|
|
|
|
formdef.data_class().wipe()
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.store()
|
|
|
|
jump.by = [role.id]
|
|
workflow.store()
|
|
|
|
access = ApiAccess()
|
|
access.name = 'test'
|
|
access.access_identifier = 'test'
|
|
access.access_key = '12345'
|
|
access.roles = [role2]
|
|
access.store()
|
|
|
|
get_app(pub).post(
|
|
sign_uri(formdata.get_url() + 'jump/trigger/XXX/', orig='test', key='12345'), status=403
|
|
)
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st1' # no change
|
|
|
|
access.roles = [role]
|
|
access.store()
|
|
|
|
get_app(pub).post(
|
|
sign_uri(formdata.get_url() + 'jump/trigger/XXX/', orig='test', key='12345'), status=200
|
|
)
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st2'
|
|
assert formdef.data_class().get(formdata.id).evolution[-1].who is None
|
|
|
|
|
|
def test_workflow_trigger_http_auth_access(pub, local_user):
|
|
ApiAccess.wipe()
|
|
pub.role_class.wipe()
|
|
role = pub.role_class(name='xxx')
|
|
role.store()
|
|
role2 = pub.role_class(name='xxx2')
|
|
role2.store()
|
|
|
|
workflow = Workflow(name='test')
|
|
st1 = workflow.add_status('Status1', 'st1')
|
|
jump = st1.add_action('jump')
|
|
jump.trigger = 'XXX'
|
|
jump.status = 'st2'
|
|
workflow.add_status('Status2', 'st2')
|
|
workflow.store()
|
|
|
|
FormDef.wipe()
|
|
formdef = FormDef()
|
|
formdef.name = 'test'
|
|
formdef.fields = []
|
|
formdef.workflow_id = workflow.id
|
|
formdef.store()
|
|
|
|
formdef.data_class().wipe()
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.store()
|
|
|
|
jump.by = [role.id]
|
|
workflow.store()
|
|
|
|
access = ApiAccess()
|
|
access.name = 'test'
|
|
access.access_identifier = 'test'
|
|
access.access_key = '12345'
|
|
access.roles = [role2]
|
|
access.store()
|
|
|
|
app = get_app(pub)
|
|
app.set_authorization(('Basic', ('test', '12345')))
|
|
app.post(formdata.get_url() + 'jump/trigger/XXX/', status=403)
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st1' # no change
|
|
|
|
access.roles = [role]
|
|
access.store()
|
|
|
|
app.post(formdata.get_url() + 'jump/trigger/XXX/', headers={'accept': 'application/json'}, status=200)
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st2'
|
|
assert formdef.data_class().get(formdata.id).evolution[-1].who is None
|
|
|
|
|
|
def get_latest_comment(formdata):
|
|
for evolution in reversed(formdata.evolution):
|
|
for part in reversed(evolution.parts):
|
|
if isinstance(part, JournalEvolutionPart):
|
|
return part.content
|
|
|
|
|
|
def test_workflow_global_webservice_trigger(pub, local_user, admin_user):
|
|
workflow = Workflow(name='test')
|
|
workflow.add_status('Status1', 'st1')
|
|
|
|
ac1 = workflow.add_global_action('Action', 'ac1')
|
|
trigger = ac1.append_trigger('webservice')
|
|
trigger.identifier = 'plop'
|
|
|
|
add_to_journal = ac1.add_action('register-comment', id='_add_to_journal')
|
|
add_to_journal.comment = 'HELLO WORLD'
|
|
|
|
workflow.store()
|
|
|
|
FormDef.wipe()
|
|
formdef = FormDef()
|
|
formdef.name = 'test'
|
|
formdef.fields = []
|
|
formdef.workflow_id = workflow.id
|
|
formdef.store()
|
|
|
|
formdef.data_class().wipe()
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.store()
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st1'
|
|
|
|
# call to undefined hook
|
|
get_app(pub).post(sign_uri(formdata.get_url() + 'hooks/XXX/'), status=404)
|
|
get_app(pub).post(sign_uri(formdata.get_api_url() + 'hooks/XXX/'), status=404)
|
|
|
|
# anonymous call
|
|
get_app(pub).post(formdata.get_url() + 'hooks/plop/', status=200)
|
|
assert get_latest_comment(formdef.data_class().get(formdata.id)) == 'HELLO WORLD'
|
|
|
|
add_to_journal.comment = 'HELLO WORLD 2'
|
|
workflow.store()
|
|
get_app(pub).post(formdata.get_api_url() + 'hooks/plop/', status=200)
|
|
assert get_latest_comment(formdef.data_class().get(formdata.id)) == 'HELLO WORLD 2'
|
|
|
|
# call requiring user
|
|
add_to_journal.comment = 'HELLO WORLD 3'
|
|
trigger.roles = ['logged-users']
|
|
workflow.store()
|
|
get_app(pub).post(formdata.get_api_url() + 'hooks/plop/', status=403)
|
|
get_app(pub).post(sign_uri(formdata.get_api_url() + 'hooks/plop/'), status=200)
|
|
formdata.refresh_from_storage()
|
|
assert get_latest_comment(formdata) == 'HELLO WORLD 3'
|
|
assert formdata.evolution[-1].parts[-1].event == 'global-api-trigger'
|
|
assert formdata.evolution[-1].parts[-1].event_args == (ac1.id,)
|
|
resp = login(get_app(pub), username='admin', password='admin').get(
|
|
formdata.get_backoffice_url() + 'inspect'
|
|
)
|
|
# check tracing link is correct:
|
|
assert '/global-actions/ac1/items/_add_to_journal/' in resp.text
|
|
|
|
# call requiring roles
|
|
add_to_journal.comment = 'HELLO WORLD 4'
|
|
trigger.roles = ['logged-users']
|
|
workflow.store()
|
|
pub.role_class.wipe()
|
|
role = pub.role_class(name='xxx')
|
|
role.store()
|
|
trigger.roles = [role.id]
|
|
workflow.store()
|
|
get_app(pub).post(sign_uri(formdata.get_api_url() + 'hooks/plop/'), status=403)
|
|
get_app(pub).post(sign_uri(formdata.get_api_url() + 'hooks/plop/', user=local_user), status=403)
|
|
|
|
local_user.roles = [role.id]
|
|
local_user.store()
|
|
get_app(pub).post(sign_uri(formdata.get_api_url() + 'hooks/plop/', user=local_user), status=200)
|
|
assert get_latest_comment(formdef.data_class().get(formdata.id)) == 'HELLO WORLD 4'
|
|
|
|
# make sure a button is not added for external triggers
|
|
account = PasswordAccount(id='user')
|
|
account.set_password('user')
|
|
account.user_id = local_user.id
|
|
account.store()
|
|
|
|
local_user.is_admin = True
|
|
local_user.store()
|
|
app = login(get_app(pub), username='user', password='user')
|
|
resp = app.get(formdata.get_backoffice_url())
|
|
assert not resp.pyquery('button[value="Action"]')
|
|
|
|
account.remove_self()
|
|
local_user.is_admin = False
|
|
local_user.store()
|
|
|
|
# call adding data
|
|
add_to_journal.comment = 'HELLO {{plop_test}}'
|
|
workflow.store()
|
|
get_app(pub).post_json(
|
|
sign_uri(formdata.get_api_url() + 'hooks/plop/', user=local_user), {'test': 'foobar'}, status=200
|
|
)
|
|
# (django templating make it turn into HTML)
|
|
assert get_latest_comment(formdef.data_class().get(formdata.id)) == '<div>HELLO foobar</div>'
|
|
|
|
# call adding data but with no actions
|
|
ac1.items = []
|
|
workflow.store()
|
|
get_app(pub).post_json(
|
|
sign_uri(formdata.get_api_url() + 'hooks/plop/', user=local_user), {'test': 'BAR'}, status=200
|
|
)
|
|
assert formdef.data_class().get(formdata.id).workflow_data == {'plop': {'test': 'BAR'}}
|
|
|
|
|
|
def test_workflow_global_webservice_trigger_no_trailing_slash(pub, local_user):
|
|
workflow = Workflow(name='test')
|
|
workflow.add_status('Status1', 'st1')
|
|
|
|
ac1 = workflow.add_global_action('Action', 'ac1')
|
|
trigger = ac1.append_trigger('webservice')
|
|
trigger.identifier = 'plop'
|
|
|
|
add_to_journal = ac1.add_action('register-comment', id='_add_to_journal')
|
|
add_to_journal.comment = 'HELLO WORLD'
|
|
|
|
workflow.store()
|
|
|
|
FormDef.wipe()
|
|
formdef = FormDef()
|
|
formdef.name = 'test'
|
|
formdef.fields = []
|
|
formdef.workflow_id = workflow.id
|
|
formdef.store()
|
|
|
|
formdef.data_class().wipe()
|
|
formdata = formdef.data_class()()
|
|
formdata.just_created()
|
|
formdata.store()
|
|
assert formdef.data_class().get(formdata.id).status == 'wf-st1'
|
|
|
|
# call to undefined hook
|
|
get_app(pub).post(sign_uri(formdata.get_url() + 'hooks/XXX'), status=404)
|
|
get_app(pub).post(sign_uri(formdata.get_api_url() + 'hooks/XXX'), status=404)
|
|
|
|
# anonymous call
|
|
get_app(pub).post(formdata.get_url() + 'hooks/plop', status=200)
|
|
assert get_latest_comment(formdef.data_class().get(formdata.id)) == 'HELLO WORLD'
|