From e80b449f2fded750de9aa2677a7a5978d22a1b57 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Tue, 26 Nov 2019 11:03:01 +0100
Subject: [PATCH 1/3] tracking_code: add tests on formdata access (#38073)

---
 tests/test_tracking_code.py | 384 +++++++++++++++++++++++++++++++++++-
 1 file changed, 378 insertions(+), 6 deletions(-)

diff --git a/tests/test_tracking_code.py b/tests/test_tracking_code.py
index 255ef050..4016680e 100644
--- a/tests/test_tracking_code.py
+++ b/tests/test_tracking_code.py
@@ -1,28 +1,201 @@
 import pytest
 
+from quixote import cleanup
+from wcs import fields
+from wcs.admin.settings import UserFieldsFormDef
 from wcs.formdef import FormDef
+from wcs.qommon.ident.password_accounts import PasswordAccount
+from wcs.roles import Role
+from wcs.wf.form import FormWorkflowStatusItem, WorkflowFormFieldsFormDef
+from wcs.workflows import Workflow, ChoiceWorkflowStatusItem
 
-from utilities import create_temporary_pub, clean_temporary_pub
+from utilities import get_app, login, create_temporary_pub, clean_temporary_pub
 
 
 def pytest_generate_tests(metafunc):
     if 'pub' in metafunc.fixturenames:
         metafunc.parametrize('pub', ['pickle', 'sql'], indirect=True)
 
+def teardown_module(module):
+    clean_temporary_pub()
 
 @pytest.fixture
 def pub(request):
-    return create_temporary_pub(sql_mode=(request.param == 'sql'))
+    cleanup()
+    pub = create_temporary_pub(sql_mode=(request.param == 'sql'))
+    pub.cfg['identification'] = {'methods': ['password']}
+    pub.cfg['language'] = {'language': 'en'}
+    pub.write_cfg()
+    return pub
+
+@pytest.fixture
+def users(pub):
+    def create_user(name):
+        user = pub.user_class()
+        user.email = '%s@example.net' % name
+        user.name = name
+        user.store()
+        account = PasswordAccount(id=name)
+        account.set_password(name)
+        account.user_id = user.id
+        account.store()
 
+        user_formdef = UserFieldsFormDef(pub)
+        user_formdef.fields = [
+            fields.StringField(
+                id='_first_name', label='first name',
+                type='string', extra_css_class='autocomplete-given-name'),
+        ]
+        user_formdef.store()
+        user.form_data = {'_first_name': name}
+        user.set_attributes_from_formdata(user.form_data)
+        user.store()
+        return user
 
-def teardown_module(module):
-    clean_temporary_pub()
+    pub.user_class.wipe()
+    PasswordAccount.wipe()
+    user1 = create_user('user1')
+    user2 = create_user('user2')
+    agent1 = create_user('agent1')
+    agent2 = create_user('agent2')
+    Role.wipe()
+    role1 = Role(name='Submitters')
+    role1.allows_backoffice_access = True
+    role1.store()
+    role2 = Role(name='Receivers')
+    role2.store()
+    agent1.roles = [role1.id]
+    agent1.store()
+    agent2.roles = [role2.id]
+    agent2.store()
+    return {
+        'anonymous': None,
+        'user1': user1,
+        'user2': user2,
+        'agent1': agent1,
+        'agent2': agent2,
+    }
+
+@pytest.fixture
+def simple_formdef(users):
+    FormDef.wipe()
+    formdef = FormDef()
+    formdef.name = 'test'
+    formdef.fields = []
+    formdef.backoffice_submission_roles = users['agent1'].roles[:]
+    formdef.workflow_roles = {'_receiver': users['agent2'].roles[0]}
+    formdef.enable_tracking_codes = True
+    workflow = Workflow.get_default_workflow()
+    formdef.workflow_id = workflow.id
+    formdef.store()
+    return formdef
+
+@pytest.fixture
+def prefill_formdef(users):
+    FormDef.wipe()
+    formdef = FormDef()
+    formdef.name = 'test'
+    formdef.fields = [
+        fields.PageField(id='0', label='1st page', type='page'),
+        fields.CommentField(
+            id='2', type='comment', display_locations=['validation', 'summary'],
+            label='{{form_user_display_name}}'),
+        fields.StringField(
+            id='3', label='prefill-string-page1', required=False,
+            prefill={'type': 'string', 'value': '{{form_user_display_name}}'}),
+        fields.StringField(
+            id='p3', label='prefill-user-page1', required=False,
+            prefill={'type': 'user', 'value': '_first_name'}),
 
+        # updated on draft using tracking code
+        fields.PageField(id='4', label='2nd page', type='page'),
+        fields.StringField(
+            id='4', label='prefill-string-page2', required=False,
+            prefill={'type': 'string', 'value': '{{form_user_display_name}}'}),
+        fields.StringField(
+            id='p4', label='prefill-user-page2', required=False,
+            prefill={'type': 'user', 'value': '_first_name'}),
+    ]
+    formdef.backoffice_submission_roles = users['agent1'].roles[:]
+    formdef.workflow_roles = {'_receiver': users['agent2'].roles[0]}
+    formdef.enable_tracking_codes = True
+    formdef.store()
+
+    Workflow.wipe()
+    workflow = Workflow(name='test')
+    st1 = workflow.add_status('Status1')
+    st2 = workflow.add_status('Status2')
+
+    display_form = FormWorkflowStatusItem()
+    display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
+    display_form.formdef.fields = [
+        # updated on workflow using tracking code
+        fields.StringField(
+            id='5', label='prefill-string-workflow', required=False,
+            prefill={'type': 'string', 'value': '{{form_user_display_name}}'}),
+        fields.StringField(
+            id='p5', label='prefill-user-workflow', required=False,
+            prefill={'type': 'user', 'value': '_first_name'}),
+    ]
+    display_form.by = ['_submitter']
+    display_form.parent = st2
+
+    jump = ChoiceWorkflowStatusItem()
+    jump.id = '_jump'
+    jump.label = 'Go form'
+    jump.by = ['_submitter']
+    jump.status = st2.id
+    jump.parent = st1
+
+    st1.items.append(jump)
+    st2.items.append(display_form)
+    workflow.store()
+
+    formdef.workflow_id = workflow.id
+    formdef.store()
+    return formdef
+
+def submit_demand(pub, formdef, user, is_front=True, nb_submit=1):
+    assert nb_submit > 0
+    app = get_app(pub)
+
+    if user:
+        app = login(app, username=user.name, password=user.name)
+    if is_front:
+        resp = app.get('/test/')
+        assert '<h3>Tracking code</h3>' in resp.text
+    else:
+        resp = app.get('/backoffice/submission/test/')
+        assert '<h3>Tracking Code</h3>' in resp.text
+
+    formdef.data_class().wipe()
+    resp = resp.form.submit('submit')
+    for i in range(1, nb_submit):
+        resp = resp.form.submit('submit')
+    assert formdef.data_class().count() == 1
+
+def get_comment_value(resp):
+    html = resp.html.find('div', attrs={'class': 'comment-field'})
+    if html:
+        return html.p.text
+    else:  # empty comment is not displayed in summary
+        return ''
+
+def get_field_value(resp, field_label):
+    # validation page
+    for html in resp.html.find_all('div', attrs={'class': 'WcsExtraStringWidget'}):
+        if html.label.text == field_label:
+            return html.input.get('value') or ''
+    # summary
+    for html in resp.html.find_all('div', attrs={'class': 'field'}):
+        if html.span.text == field_label:
+            return html.div.text
+    return ''  # empty field is not displayed in summary
 
 def test_tracking_code(pub):
     klass = pub.tracking_code_class
     klass.wipe()
 
     code = klass()
     code.store()
     code = klass()
@@ -45,17 +218,16 @@ def test_tracking_code(pub):
     code.store()
     assert klass.count() == 2
 
     assert klass.get(code.id).formdef_id == code.formdef_id
     assert klass.get(code.id).formdata_id == code.formdata_id
 
     assert klass.get(code.id).formdata.tracking_code == code.id
 
-
 def test_tracking_code_duplicate(pub):
     klass = pub.tracking_code_class
     klass.wipe()
 
     code = klass()
     code.store()
     code_id = code.id
 
@@ -72,10 +244,210 @@ def test_tracking_code_duplicate(pub):
             return code_id
         else:
             marker['done'] = True
             return real_get_new_id(cls)
     klass.get_new_id = fake_get_new_id
     code.store()
     klass.get_new_id = real_get_new_id
 
-    assert marker.get('done') # makes sure we got to the real new id code
+    assert marker.get('done')  # makes sure we got to the real new id code
     assert klass.count() == 2
+
+@pytest.mark.parametrize("submitter, is_front, agent, expected_access", [
+    ('anonymous', True, 'anonymous', 'login'),
+    ('anonymous', True, 'user1', 'deny'),
+    ('anonymous', True, 'user2', 'deny'),
+    ('anonymous', True, 'agent1', 'deny'),
+    ('anonymous', True, 'agent2', 'deny'),
+    ('agent1', False, 'anonymous', 'login'),
+    ('agent1', False, 'user1', 'deny'),
+    ('agent1', False, 'user2', 'deny'),
+    ('agent1', False, 'agent1', 'deny'),
+    ('agent1', False, 'agent2', 'deny'),
+    ('user1', True, 'anonymous', 'login'),
+    ('user1', True, 'user1', 'front'),
+    ('user1', True, 'user2', 'deny'),
+    ('user1', True, 'agent1', 'deny'),
+    ('user1', True, 'agent2', 'deny'),
+])
+def test_fo_direct_access_to_draft(nocache, pub, users, simple_formdef,
+                                   submitter, is_front, agent, expected_access):
+    submit_demand(pub, simple_formdef, users[submitter], is_front, 1)
+    formdata = simple_formdef.data_class().select()[0]
+    assert formdata.is_draft() is True
+    formdata_id = formdata.id
+
+    app = get_app(pub)
+    if agent != 'anonymous':
+        app = login(app, username=users[agent].name, password=users[agent].name)
+    resp = app.get('/test/%s' % formdata_id)
+    if expected_access == 'deny':
+        assert resp.follow(status=403)
+    elif expected_access == 'login':
+        resp = resp.follow()
+        assert resp.location.startswith('http://example.net/login/?next=')
+    elif expected_access == 'front':
+        resp = resp.follow()
+        assert 'http://example.net/test/?mt=' in resp.location
+        resp = resp.follow()
+        assert '<title>Forms - test</title>' in resp.text
+    else:
+        assert expected_access in ('login', 'deny', 'front')
+
+@pytest.mark.parametrize("submitter, is_front, agent, expected_access", [
+    ('anonymous', True, 'anonymous', 'login'),
+    ('anonymous', True, 'user1', 'deny'),
+    ('anonymous', True, 'user2', 'deny'),
+    ('anonymous', True, 'agent1', 'deny'),
+    ('anonymous', True, 'agent2', 'back'),
+    ('agent1', False, 'anonymous', 'login'),
+    ('agent1', False, 'user1', 'deny'),
+    ('agent1', False, 'user2', 'deny'),
+    ('agent1', False, 'agent1', 'deny'),
+    ('agent1', False, 'agent2', 'back'),
+    ('user1', True, 'anonymous', 'login'),
+    ('user1', True, 'user1', 'front'),
+    ('user1', True, 'user2', 'deny'),
+    ('user1', True, 'agent1', 'deny'),
+    ('user1', True, 'agent2', 'back'),
+])
+def test_fo_direct_access_to_demand(nocache, pub, users, simple_formdef,
+                                    submitter, is_front, agent, expected_access):
+    submit_demand(pub, simple_formdef, users[submitter], is_front, 2)
+    formdata = simple_formdef.data_class().select()[0]
+    assert formdata.is_draft() is False
+    formdata_id = formdata.id
+
+    app = get_app(pub)
+    if agent != 'anonymous':
+        app = login(app, username=users[agent].name, password=users[agent].name)
+    resp = app.get('/test/%s' % formdata_id)
+    assert resp.location == 'http://example.net/test/%s/' % formdata_id
+    if expected_access == 'deny':
+        assert resp.follow(status=403)
+    elif expected_access == 'login':
+        resp = resp.follow()
+        assert resp.location.startswith('http://example.net/login/?next=')
+    elif expected_access == 'front':
+        resp = resp.follow()
+        assert '<title>Forms - test</title>' in resp.text
+    elif expected_access == 'back':
+        resp = resp.follow()
+        assert '/backoffice/management/test/%s/' % formdata_id in resp.location
+        resp = resp.follow()
+        assert 'Back Office of wcs - test - %s' % formdata_id in resp.text
+    else:
+        assert expected_access in ('login', 'deny', 'front', 'back')
+
+@pytest.mark.parametrize("submitter, is_front, agent, expected_access, expected_owner", [
+    ('anonymous', True, 'anonymous', 'allow', 'anonymous'),
+    ('anonymous', True, 'user1', 'allow', 'anonymous'),
+    ('anonymous', True, 'user2', 'allow', 'anonymous'),
+    ('anonymous', True, 'agent1', 'allow', 'anonymous'),
+    ('anonymous', True, 'agent2', 'allow', 'anonymous'),
+    ('agent1', False, 'anonymous', 'allow', 'anonymous'),
+    ('agent1', False, 'user1', 'allow', 'anonymous'),
+    ('agent1', False, 'user2', 'allow', 'anonymous'),
+    ('agent1', False, 'agent1', 'allow', 'anonymous'),
+    ('agent1', False, 'agent2', 'allow', 'anonymous'),
+    ('user1', True, 'anonymous', 'allow', 'user1'),
+    ('user1', True, 'user1', 'allow', 'user1'),
+    ('user1', True, 'user2', 'allow', 'user1'),
+    ('user1', True, 'agent1', 'allow', 'user1'),
+    ('user1', True, 'agent2', 'allow', 'user1'),
+])
+def test_fo_indirect_access_to_draft(nocache, pub, users, prefill_formdef,
+         submitter, is_front, agent, expected_access, expected_owner):
+    user = users[agent]
+    expected_owner = users[expected_owner]
+    expected_new_owner = user
+
+    submit_demand(pub, prefill_formdef, users[submitter], is_front, 1)
+    formdata = prefill_formdef.data_class().select()[0]
+    assert formdata.is_draft() is True
+    formdata_id = formdata.id
+
+    app = get_app(pub)
+    if user:
+        app = login(app, username=user.name, password=user.name)
+    resp = app.get('/')
+    resp.forms[0]['code'] = formdata.tracking_code
+    resp = resp.forms[0].submit()
+    resp = resp.follow()
+    if expected_access == 'login':
+        assert resp.location.startswith('http://example.net/login/?ReturnUrl=')
+        return
+    resp = resp.follow()
+    resp = resp.follow()
+    assert '<title>Forms - test</title>' in resp.text
+    resp = resp.forms[1].submit('submit')  # submit page 2
+
+    formdata = prefill_formdef.data_class().select()[0]
+    assert formdata.id == formdata_id  # formdata must be reloaded here
+    formdata_user = getattr(formdata.user, 'name', '')
+    owner_value = getattr(expected_owner, 'name', '')
+    new_owner_value = getattr(expected_new_owner, 'name', '')
+
+    if expected_new_owner:
+        assert formdata_user == new_owner_value
+    else:  # formdata_user is not overidden by anonymous user
+        assert formdata_user == owner_value
+    assert get_comment_value(resp) == new_owner_value
+    assert get_field_value(resp, 'prefill-string-page1') == owner_value
+    assert get_field_value(resp, 'prefill-user-page1') == owner_value
+    assert get_field_value(resp, 'prefill-string-page2') == new_owner_value
+    assert get_field_value(resp, 'prefill-user-page2') == new_owner_value
+
+@pytest.mark.parametrize("submitter, is_front, agent, expected_access, expected_owner", [
+    ('anonymous', True, 'anonymous', 'allow', 'anonymous'),
+    ('anonymous', True, 'user1', 'allow', 'anonymous'),
+    ('anonymous', True, 'user2', 'allow', 'anonymous'),
+    ('anonymous', True, 'agent1', 'allow', 'anonymous'),
+    ('anonymous', True, 'agent2', 'allow', 'anonymous'),
+    ('agent1', False, 'anonymous', 'allow', 'anonymous'),
+    ('agent1', False, 'user1', 'allow', 'anonymous'),
+    ('agent1', False, 'user2', 'allow', 'anonymous'),
+    ('agent1', False, 'agent1', 'allow', 'anonymous'),
+    ('agent1', False, 'agent2', 'allow', 'anonymous'),
+    ('user1', True, 'anonymous', 'allow', 'user1'),
+    ('user1', True, 'user1', 'allow', 'user1'),
+    ('user1', True, 'user2', 'allow', 'user1'),
+    ('user1', True, 'agent1', 'allow', 'user1'),
+    ('user1', True, 'agent2', 'allow', 'user1'),
+])
+def test_fo_indirect_access_to_demand(nocache, pub, users, prefill_formdef,
+         submitter, is_front, agent, expected_access, expected_owner):
+    user = users[agent]
+    expected_owner = users[expected_owner]
+    expected_new_owner = user
+
+    submit_demand(pub, prefill_formdef, users[submitter], is_front, 3)
+    formdata = prefill_formdef.data_class().select()[0]
+    assert formdata.is_draft() is False
+    formdata_id = formdata.id
+
+    app = get_app(pub)
+    if user:
+        app = login(app, username=user.name, password=user.name)
+    resp = app.get('/')
+    resp.forms[0]['code'] = formdata.tracking_code
+    resp = resp.forms[0].submit()
+    resp = resp.follow()
+    if expected_access == 'login':
+        return
+    resp = resp.follow()
+    assert '<title>Forms - test</title>' in resp.text
+    resp = resp.form.submit('button_jump')  # go to display_form
+    resp = resp.follow()
+
+    formdata = prefill_formdef.data_class().select()[0]
+    assert formdata.id == formdata_id  # formdata must be reloaded here
+    formdata_user = getattr(formdata.user, 'name', '')
+    owner_value = getattr(expected_owner, 'name', '')
+    new_owner_value = getattr(expected_new_owner, 'name', '')
+
+    assert formdata_user == owner_value
+    assert get_comment_value(resp) == owner_value
+    assert get_field_value(resp, 'prefill-string-page1') == owner_value
+    assert get_field_value(resp, 'prefill-user-page1') == owner_value
+    assert get_field_value(resp, 'prefill-string-workflow') == owner_value
+    assert get_field_value(resp, 'prefill-user-workflow') == new_owner_value
-- 
2.25.0

