# -*- coding: utf-8 -*-

import datetime
from email.mime.text import MIMEText
import json
import logging
import os
import re
import shutil
import tarfile
import time
import xml.etree.ElementTree as ET
import zipfile

try:
    import lasso
except ImportError:
    lasso = None

import pytest
from webtest import Upload
import mock

from django.utils import six
from django.utils.six import StringIO, BytesIO
from django.utils.six.moves.urllib import parse as urlparse

from quixote import cleanup, get_publisher
from quixote.http_request import Upload as QuixoteUpload

from wcs.qommon import errors, sessions
from wcs.qommon.form import UploadedFile
from wcs.qommon.ident.password_accounts import PasswordAccount
from wcs.qommon.http_request import HTTPRequest
from wcs.qommon.template import get_current_theme
from wcs.admin.settings import UserFieldsFormDef
from wcs.categories import Category
from wcs.data_sources import NamedDataSource
from wcs.wscalls import NamedWsCall
from wcs.roles import Role
from wcs.workflows import (Workflow, DisplayMessageWorkflowStatusItem,
        WorkflowCriticalityLevel, WorkflowBackofficeFieldsFormDef,
        CommentableWorkflowStatusItem)
from wcs.wf.export_to_model import ExportToModel
from wcs.wf.jump import JumpWorkflowStatusItem
from wcs.wf.register_comment import RegisterCommenterWorkflowStatusItem
from wcs.wf.wscall import WebserviceCallStatusItem
from wcs.formdef import FormDef
from wcs.carddef import CardDef
from wcs import fields

from utilities import get_app, login, create_temporary_pub, clean_temporary_pub, HttpRequestsMocking

def pytest_generate_tests(metafunc):
    if 'pub' in metafunc.fixturenames:
        metafunc.parametrize('pub', ['pickle', 'sql', 'pickle-templates'], indirect=True)

@pytest.fixture
def pub(request):
    pub = create_temporary_pub(
            sql_mode=bool('sql' in request.param),
            templates_mode=bool('templates' in request.param)
            )

    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()

    return pub

def create_superuser(pub):
    if pub.user_class.select(lambda x: x.name == 'admin'):
        user1 = pub.user_class.select(lambda x: x.name == 'admin')[0]
        user1.is_admin = True
        user1.store()
        return user1

    user1 = pub.user_class(name='admin')
    user1.is_admin = True
    user1.store()

    account1 = PasswordAccount(id='admin')
    account1.set_password('admin')
    account1.user_id = user1.id
    account1.store()

    return user1

def create_role():
    Role.wipe()
    role = Role(name='foobar')
    role.store()
    return role

def teardown_module(module):
    clean_temporary_pub()

def test_empty_site(pub):
    resp = get_app(pub).get('/backoffice/')
    resp = resp.click('Users', index=0)
    resp = resp.click('New User')
    resp = get_app(pub).get('/backoffice/')
    resp = resp.click('Settings', index=0)

def test_empty_site_but_idp_settings(pub):
    pub.cfg['idp'] = {'xxx': {}}
    pub.write_cfg()
    resp = get_app(pub).get('/backoffice/')
    assert resp.location == 'http://example.net/login/?next=http%3A%2F%2Fexample.net%2Fbackoffice%2F'

def test_with_user(pub):
    create_superuser(pub)
    resp = get_app(pub).get('/backoffice/', status=302)
    assert resp.location == 'http://example.net/login/?next=http%3A%2F%2Fexample.net%2Fbackoffice%2F'

def test_with_superuser(pub):
    create_superuser(pub)
    app = login(get_app(pub))
    app.get('/backoffice/')

def test_admin_redirect(pub):
    create_superuser(pub)
    app = login(get_app(pub))
    assert app.get('/admin/whatever', status=302).location == 'http://example.net/backoffice/whatever'

def test_admin_for_all(pub):
    user = create_superuser(pub)
    role = create_role()

    try:
        open(os.path.join(pub.app_dir, 'ADMIN_FOR_ALL'), 'w').close()
        resp = get_app(pub).get('/backoffice/', status=200)
        # check there is a CSS class
        assert 'class="admin-for-all"' in resp.text
        # check there are menu items
        resp.click('Management', index=0)
        resp.click('Forms Workshop', index=0)
        resp.click('Settings', index=0)

        # cheeck it's possible to get inside the subdirectories
        resp = get_app(pub).get('/backoffice/settings/', status=200)

        pub.cfg['admin-permissions'] = {'settings': [role.id]}
        pub.write_cfg()
        resp = get_app(pub).get('/backoffice/settings/', status=200)

        # check it doesn't work with a non-empty ADMIN_FOR_ALL file
        fd = open(os.path.join(pub.app_dir, 'ADMIN_FOR_ALL'), 'w')
        fd.write('x.x.x.x')
        fd.close()
        resp = get_app(pub).get('/backoffice/settings/', status=302)

        # check it works if the file contains the user IP address
        fd = open(os.path.join(pub.app_dir, 'ADMIN_FOR_ALL'), 'w')
        fd.write('127.0.0.1')
        fd.close()
        resp = get_app(pub).get('/backoffice/settings/', status=200)

        # check it's also ok if the user is logged in but doesn't have the
        # permissions
        user.is_admin = False
        user.store()
        resp = login(get_app(pub)).get('/backoffice/settings/', status=200)
        # check there are menu items
        resp.click('Management', index=0)
        resp.click('Forms Workshop', index=0)
        resp.click('Settings', index=0)

    finally:
        if 'admin-permissions' in pub.cfg:
            del pub.cfg['admin-permissions']
            pub.write_cfg()
        os.unlink(os.path.join(pub.app_dir, 'ADMIN_FOR_ALL'))
        role.remove_self()
        user.is_admin = True
        user.store()

def test_forms(pub):
    user = create_superuser(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/')
    assert 'You first have to define roles.' in resp.text
    assert not 'New Form' in resp.text

def test_forms_new(pub):
    user = create_superuser(pub)
    app = login(get_app(pub))
    create_role()

    # create a new form
    resp = app.get('/backoffice/forms/')
    assert 'New Form' in resp.text
    resp = resp.click('New Form')
    resp.forms[0]['name'] = 'form title'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    resp = resp.follow()
    assert '<h2>form title' in resp.text

    # makes sure the data has been correctly saved
    formdef = FormDef.get(1)
    assert formdef.name == 'form title'
    assert formdef.url_name == 'form-title'
    assert formdef.fields == []
    assert formdef.disabled == True
    assert formdef.last_modification_user_id == str(user.id)

def test_forms_new_popup(pub):
    FormDef.wipe()
    user = create_superuser(pub)
    app = login(get_app(pub))
    create_role()

    # create a new form
    resp = app.get('/backoffice/forms/')
    assert 'New Form' in resp.text
    resp = resp.click('New Form', extra_environ={'HTTP_X_POPUP': 'true'})
    assert 'popup-content' in resp.text
    resp.forms[0]['name'] = 'form title'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    resp = resp.follow()
    assert '<h2>form title' in resp.text

    # makes sure the data has been correctly saved
    formdef = FormDef.get(1)
    assert formdef.name == 'form title'
    assert formdef.url_name == 'form-title'
    assert formdef.fields == []
    assert formdef.disabled == True
    assert formdef.last_modification_user_id == str(user.id)

def assert_option_display(resp, label, value):
    option_line = re.findall('%s.*%s' % (label, value), resp.text, re.DOTALL)
    assert option_line
    assert not '</li>' in option_line

def test_forms_edit(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')

    # try changing an option

    # confirmation page
    assert_option_display(resp, 'Confirmation Page', 'Enabled')
    resp = resp.click('Confirmation Page')
    assert resp.forms[0]['confirmation'].checked
    resp.forms[0]['confirmation'].checked = False
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    resp = resp.follow()
    assert_option_display(resp, 'Confirmation Page', 'Disabled')
    assert FormDef.get(1).confirmation == False

    # try cancel button
    resp = resp.click('Confirmation Page')
    assert resp.forms[0]['confirmation'].checked is False
    resp.forms[0]['confirmation'].checked = True
    resp = resp.forms[0].submit('cancel')
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    resp = resp.follow()
    assert_option_display(resp, 'Confirmation Page', 'Disabled')
    assert FormDef.get(1).confirmation == False

    # Limit to one form
    assert_option_display(resp, 'Limit to one form', 'Disabled')
    resp = resp.click('Limit to one form')
    assert resp.forms[0]['only_allow_one'].checked is False
    resp.forms[0]['only_allow_one'].checked = True
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    resp = resp.follow()
    assert_option_display(resp, 'Limit to one form', 'Enabled')
    assert FormDef.get(1).only_allow_one is True

    # Tracking code
    assert_option_display(resp, 'Tracking Code', 'Disabled')
    resp = resp.click('Tracking Code')
    assert resp.forms[0]['enable_tracking_codes'].checked is False
    resp.forms[0]['enable_tracking_codes'].checked = True
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    resp = resp.follow()
    assert_option_display(resp, 'Tracking Code', 'Enabled')
    assert FormDef.get(1).enable_tracking_codes is True

    # CAPTCHA
    assert_option_display(resp, 'CAPTCHA for anonymous users', 'Disabled')
    resp = resp.click('CAPTCHA for anonymous users')
    assert resp.forms[0]['has_captcha'].checked is False
    resp.forms[0]['has_captcha'].checked = True
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    resp = resp.follow()
    assert_option_display(resp, 'CAPTCHA for anonymous users', 'Enabled')
    assert FormDef.get(1).has_captcha is True

    # Appearance
    assert_option_display(resp, 'Appearance', 'Standard')
    resp = resp.click('Appearance')
    assert resp.forms[0]['appearance_keywords'].value == ''
    resp.forms[0]['appearance_keywords'] = 'foobar'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    resp = resp.follow()
    assert_option_display(resp, 'Appearance', 'foobar')
    assert FormDef.get(1).appearance_keywords == 'foobar'

    # Publication
    assert_option_display(resp, 'Online Status', 'Active')
    resp = resp.click('Online Status')
    assert resp.forms[0]['disabled'].checked is False
    resp.forms[0]['disabled'].checked = True
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    resp = resp.follow()
    assert_option_display(resp, 'Online Status', 'Disabled')
    assert FormDef.get(1).disabled is True

    resp = resp.click('Online Status')
    assert resp.forms[0]['disabled'].checked is True
    resp.forms[0]['disabled_redirection'] = 'http://www.example.net'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    resp = resp.follow()
    assert_option_display(resp, 'Online Status', 'Redirected')
    assert FormDef.get(1).disabled is True
    assert FormDef.get(1).disabled_redirection == 'http://www.example.net'

    resp = resp.click('Online Status')
    resp.forms[0]['disabled'].checked = False
    resp.forms[0]['expiration_date'] = '2000-01-01' # this is past(tm)
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    resp = resp.follow()
    assert_option_display(resp, 'Online Status', 'Inactive by date')

    # enable geolocation
    resp = resp.click('Geolocation')
    resp.forms[0]['geoloc_label'] = 'Foobar'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    resp = resp.follow()
    assert_option_display(resp, 'Geolocation', 'Enabled')
    assert FormDef.get(formdef.id).geolocations == {'base': 'Foobar'}

    # and disable it
    resp = resp.click('Geolocation')
    resp.forms[0]['geoloc_label'] = ''
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    resp = resp.follow()
    assert_option_display(resp, 'Geolocation', 'Disabled')
    assert FormDef.get(formdef.id).geolocations is None

def test_form_title_change(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))

    resp = app.get('/backoffice/forms/1/')
    resp = resp.click('change title')
    assert resp.form['name'].value == 'form title'
    assert 'data-slug-sync' in resp.text
    assert not 'change-nevertheless' in resp.text
    resp.form['name'] = 'new title'
    resp = resp.form.submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    resp = resp.follow()
    formdef = FormDef.get(formdef.id)
    assert formdef.name == 'new title'
    assert formdef.url_name == 'form-title'
    assert formdef.internal_identifier == 'new-title'

    resp = app.get('/backoffice/forms/1/')
    resp = resp.click('change title')
    assert not 'data-slug-sync' in resp.text
    assert not 'change-nevertheless' in resp.text

    formdef.data_class()().store()
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click('change title')
    assert 'change-nevertheless' in resp.text

    formdef2 = FormDef()
    formdef2.name = 'other title'
    formdef2.fields = []
    formdef2.store()

    resp = app.get('/backoffice/forms/%s/' % formdef2.id)
    resp = resp.click('change title')
    assert resp.form['name'].value == 'other title'
    resp.form['url_name'] = formdef.url_name
    resp = resp.form.submit()
    assert 'This identifier is already used.' in resp.text

    resp.form['url_name'] = 'foobar'
    resp = resp.form.submit().follow()
    assert FormDef.get(formdef2.id).url_name == 'foobar'

def test_form_category(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    assert_option_display(resp, 'Category', 'None')

    Category.wipe()
    cat = Category(name='Foo')
    cat.store()
    cat = Category(name='Bar')
    cat.store()
    resp = app.get('/backoffice/forms/1/')
    assert 'Category' in resp.text
    assert_option_display(resp, 'Category', 'None')

def test_form_category_select(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    Category.wipe()
    cat = Category(name='Foo')
    cat.store()
    cat = Category(name='Bar')
    cat.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='category')
    resp = resp.forms[0].submit('cancel')
    assert FormDef.get(formdef.id).category_id is None

    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='category')
    resp.forms[0]['category_id'] = cat.id
    resp = resp.forms[0].submit('submit')
    assert FormDef.get(formdef.id).category_id == cat.id

def test_form_workflow(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    assert_option_display(resp, 'Workflow', 'Default')

    Workflow.wipe()
    workflow = Workflow(name='Workflow One')
    workflow.store()
    workflow = Workflow(name='Workflow Two')
    workflow.store()

    resp = app.get('/backoffice/forms/1/')
    assert_option_display(resp, 'Workflow', 'Default')

def test_form_workflow_change(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    Workflow.wipe()
    workflow = Workflow(name='Workflow One')
    workflow.store()
    workflow = Workflow(name='Workflow Two')
    workflow.possible_status = Workflow.get_default_workflow().possible_status[:]
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='workflow', index=1)
    resp = resp.forms[0].submit('cancel')
    assert FormDef.get(formdef.id).workflow_id is None

    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='workflow', index=1)
    assert 'Workflow One' not in resp.text # this workflow doesn't have any status
    resp.forms[0]['workflow_id'] = workflow.id
    resp = resp.forms[0].submit('submit')
    assert FormDef.get(formdef.id).workflow_id == workflow.id

def test_form_workflow_link(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    Workflow.wipe()
    workflow = Workflow(name='Workflow One')
    workflow.store()

    app = login(get_app(pub))

    resp = app.get('/backoffice/forms/%s/' % formdef.id)
    assert '../workflows/_default/' in resp.text

    formdef.workflow = workflow
    formdef.store()

    resp = app.get('/backoffice/forms/%s/' % formdef.id)
    assert '../workflows/%s/' % workflow.id in resp.text

    # check workflow link is not displayed if user has no access right
    pub.cfg['admin-permissions'] = {'workflows': ['x']}  #  block access
    pub.write_cfg()
    resp = app.get('/backoffice/forms/%s/' % formdef.id)
    assert '../workflows/%s/' % workflow.id not in resp.text

def test_form_workflow_remapping(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    data_class = formdef.data_class()
    data_class.wipe()
    formdata1 = data_class()
    formdata1.status = 'wf-new'
    formdata1.store()

    formdata2 = data_class()
    formdata2.status = 'draft'
    formdata2.store()

    Workflow.wipe()
    workflow = Workflow(name='Workflow One')
    workflow.store()
    workflow = Workflow(name='Workflow Two')
    # create it with a single status
    workflow.possible_status = [Workflow.get_default_workflow().possible_status[-1]]
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='workflow', index=1)
    resp.forms[0]['workflow_id'] = workflow.id
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/forms/1/workflow-status-remapping?new=2'
    resp = resp.follow()
    for status in Workflow.get_default_workflow().possible_status:
        assert resp.forms[0]['mapping-%s' % status.id]
        # there's only one possible new status
        assert len(resp.forms[0]['mapping-just_submitted'].options) == 1
    assert data_class.get(formdata1.id).status == 'wf-new'
    assert data_class.get(formdata2.id).status == 'draft'
    resp = resp.forms[0].submit()
    assert data_class.get(formdata1.id).status == 'wf-finished'
    assert data_class.get(formdata2.id).status == 'draft'

def test_form_submitter_roles(pub):
    create_superuser(pub)
    role = create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href=re.compile('^roles$'))
    resp.form['roles$element0'] = 'logged-users'
    assert not 'required_authentication_contexts' in resp.text
    resp = resp.form.submit()
    assert FormDef.get(formdef.id).roles == ['logged-users']

    # add auth contexts support
    if not pub.site_options.has_section('options'):
        pub.site_options.add_section('options')
    pub.site_options.set('options', 'auth-contexts', 'fedict')
    with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
        pub.site_options.write(fd)

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href=re.compile('^roles$'))
    assert 'required_authentication_contexts' in resp.text
    resp.form['required_authentication_contexts$element0'].checked = True
    resp = resp.form.submit()
    resp = resp.follow()
    assert FormDef.get(formdef.id).required_authentication_contexts == ['fedict']

    # check internal roles are not advertised
    role2 = Role(name='internal')
    role2.internal = True
    role2.store()

    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href=re.compile('^roles$'))
    assert len(resp.form['roles$element0'].options) == 3  # None, Logged users, foobar
    with pytest.raises(ValueError):
        resp.form['roles$element0'] = str(role2.id)

def test_form_workflow_role(pub):
    create_superuser(pub)
    role = create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='role/_receiver')
    resp = resp.forms[0].submit('cancel')

    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='role/_receiver')
    resp.forms[0]['role_id'] = role.id
    resp = resp.forms[0].submit('submit')
    assert FormDef.get(1).workflow_roles == {'_receiver': '1'}

    # check it doesn't fail if a second role with the same name exists
    role = Role(name='foobar')
    role.store()
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='role/_receiver')

def test_form_workflow_options(pub):
    create_superuser(pub)
    create_role()

    Workflow.wipe()
    workflow = Workflow(name='Workflow One')
    workflow.store()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.workflow_id = workflow.id
    formdef.workflow_options = {'2*1*body': 'xxx'}
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    assert '"workflow-options"' in resp.text

def test_form_workflow_variables(pub):
    create_superuser(pub)
    create_role()

    Workflow.wipe()
    workflow = Workflow(name='Workflow One')
    from wcs.workflows import WorkflowVariablesFieldsFormDef
    workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow)
    workflow.variables_formdef.fields.append(
            fields.StringField(id='1', varname='test', label='Test', type='string'))
    workflow.store()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.workflow_id = workflow.id
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    assert '"workflow-variables"' in resp.text

    # visit the variables page
    resp = resp.click(href='workflow-variables')

    # and set a value
    resp.forms[0]['f1'] = 'foobar'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/'

    # check the value has been correctly saved
    assert FormDef.get(formdef.id).workflow_options == {'test': 'foobar'}

    # go back to the variables page, also check value
    resp = resp.follow()
    resp = resp.click(href='workflow-variables')
    assert resp.forms[0]['f1'].value == 'foobar'
    resp.forms[0]['f1'] = 'barbaz'
    resp = resp.forms[0].submit('cancel')
    assert resp.location == 'http://example.net/backoffice/forms/1/'

    # check with a date field
    workflow.variables_formdef.fields.append(
            fields.DateField(id='2', varname='test2', label='Test2', type='date'))
    workflow.store()

    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='workflow-variables')
    resp.form['f2'] = '2016-06-17'
    resp = resp.form.submit()
    assert time.strftime('%d %m %y', FormDef.get(formdef.id).workflow_options.get('test2')) == '17 06 16'

def test_form_workflow_table_variables(pub):
    create_superuser(pub)
    create_role()

    Workflow.wipe()
    workflow = Workflow(name='Workflow One')
    from wcs.workflows import WorkflowVariablesFieldsFormDef
    workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow)
    workflow.variables_formdef.fields.append(
            fields.TableRowsField(id='1', varname='test', label='Test2',
                type='tablerows', columns=['a']))
    workflow.variables_formdef.fields.append(
            fields.StringField(id='2', varname='test2', label='Test', type='string'))
    workflow.store()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.workflow_id = workflow.id
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    assert '"workflow-variables"' in resp.text

    # visit the variables page
    resp = resp.click(href='workflow-variables')

    # and set a value
    resp.form['f1$element0$col0'] = 'foobar'
    resp.form['f2'] = 'foobar'
    resp = resp.form.submit('submit')
    assert resp.location == 'http://example.net/backoffice/forms/1/'

    # check the value has been correctly saved
    assert FormDef.get(formdef.id).workflow_options == {
            'test': [['foobar']],
            'test2': 'foobar'}

    # go back to the variables page, also check value
    resp = resp.follow()
    resp = resp.click(href='workflow-variables')
    assert resp.form['f1$element0$col0'].value == 'foobar'
    assert resp.form['f2'].value == 'foobar'

def test_form_roles(pub):
    create_superuser(pub)
    role = create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')

    resp = resp.click('User Roles')
    resp = resp.forms[0].submit('cancel')

    resp = app.get('/backoffice/forms/1/')
    resp = resp.click('User Roles')
    resp.forms[0]['roles$element0'].value = role.id
    resp = resp.forms[0].submit('submit')
    assert FormDef.get(1).roles == [role.id]

def test_form_always_advertise(pub):
    create_superuser(pub)
    role = create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')

    # Display to unlogged users
    formdef.roles = [role.id]
    formdef.store()
    resp = app.get('/backoffice/forms/1/')
    assert_option_display(resp, 'Display to unlogged users', 'Disabled')
    resp = resp.click('Display to unlogged users')
    assert resp.forms[0]['always_advertise'].checked is False
    resp.forms[0]['always_advertise'].checked = True
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    resp = resp.follow()
    assert_option_display(resp, 'Display to unlogged users', 'Enabled')
    assert FormDef.get(1).always_advertise is True

def test_form_digest_template(pub):
    create_superuser(pub)
    role = create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [fields.StringField(id='1', varname='test', label='Test', type='string')]
    formdef.store()

    formdata = formdef.data_class()()
    formdata.just_created()
    formdata.data = {'1': 'hello'}
    formdata.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')

    # Display to unlogged users
    assert_option_display(resp, 'Digest Template', 'None')
    resp = resp.click('Digest Template')
    resp.form['digest_template'] = 'X{{form_var_test}}Y'
    resp = resp.form.submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    resp = resp.follow()
    assert_option_display(resp, 'Digest Template', 'Custom')
    formdef = FormDef.get(formdef.id)
    assert formdef.digest_template == 'X{{form_var_test}}Y'

    assert 'Existing forms will be updated in the background.' in resp.text
    # afterjobs are actually run synchronously during tests; we don't have
    # to wait to check the digest has been updated:
    assert formdef.data_class().get(formdata.id).digest == 'XhelloY'

def test_form_delete(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')

    resp = resp.click(href='delete')
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/'
    resp = resp.follow()
    assert FormDef.count() == 0

def test_form_duplicate(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')

    resp = resp.click(href='duplicate')
    assert resp.location == 'http://example.net/backoffice/forms/2/'
    resp = resp.follow()
    assert FormDef.count() == 2
    assert FormDef.get(2).name == 'form title (copy)'

    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='duplicate')
    assert resp.location == 'http://example.net/backoffice/forms/3/'
    resp = resp.follow()
    assert FormDef.count() == 3
    assert FormDef.get(3).name == 'form title (copy 2)'

def test_form_export(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')

    resp = resp.click(href='export')
    xml_export = resp.text

    fd = StringIO(xml_export)
    formdef2 = FormDef.import_from_xml(fd)
    assert formdef2.name == 'form title'

def test_form_import(pub):
    user = create_superuser(pub)
    role = create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()
    formdef_xml = ET.tostring(formdef.export_to_xml(include_id=True))

    FormDef.wipe()
    assert FormDef.count() == 0

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/')
    resp = resp.click(href='import')
    resp.forms[0]['file'] = Upload('formdef.wcs', formdef_xml)
    resp = resp.forms[0].submit()
    assert FormDef.count() == 1

    # import the same formdef a second time, make sure url name and internal
    # identifier are not reused
    resp = app.get('/backoffice/forms/')
    resp = resp.click(href='import')
    resp.forms[0]['file'] = Upload('formdef.wcs', formdef_xml)
    resp = resp.forms[0].submit()
    assert FormDef.count() == 2
    assert FormDef.get(1).url_name == 'form-title'
    assert FormDef.get(2).url_name == 'form-title-1'
    assert FormDef.get(1).internal_identifier == 'form-title'
    assert FormDef.get(2).internal_identifier == 'form-title-1'

    # import a formdef with an url name that doesn't match its title,
    # it should be kept intact.
    formdef.url_name = 'xxx-other-form-title'
    formdef_xml = ET.tostring(formdef.export_to_xml(include_id=True))

    resp = app.get('/backoffice/forms/')
    resp = resp.click(href='import')
    resp.forms[0]['file'] = Upload('formdef.wcs', formdef_xml)
    resp = resp.forms[0].submit()
    assert FormDef.get(3).url_name == 'xxx-other-form-title'
    assert FormDef.get(3).internal_identifier == 'form-title-2'

    # import an invalid file
    resp = app.get('/backoffice/forms/')
    resp = resp.click(href='import')
    resp.form['file'] = Upload('formdef.wcs', b'garbage')
    resp = resp.form.submit()
    assert 'Invalid File' in resp.text

    # xml with duplicate id, fix it
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [
        fields.StringField(id='42', label='1st field', type='string'),
        fields.StringField(id='42', label='2nd field', type='string'),
    ]
    formdef_xml = ET.tostring(formdef.export_to_xml(include_id=True))
    FormDef.wipe()
    resp = app.get('/backoffice/forms/')
    resp = resp.click(href='import')
    resp.form['file'] = Upload('formdef.wcs', formdef_xml)
    resp = resp.form.submit()
    resp = resp.follow()
    assert 'form contained errors and has been automatically fixed' in resp.text
    assert FormDef.count() == 1
    assert FormDef.get(1).fields[0].id == '1'
    assert FormDef.get(1).fields[1].id == '2'

def test_form_qrcode(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')

    resp = resp.click(href='qrcode')
    assert '<div id="qrcode">' in resp.text

def test_form_description(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    assert_option_display(resp, 'Description', 'None')

    resp = resp.click('Description')
    resp.forms[0]['description'].value = '<p>Hello World</p>'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    resp = resp.follow()
    assert_option_display(resp, 'Description', 'On')

def test_form_new_field(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='fields/')
    assert 'There are not yet any fields for this form' in resp.text

    resp.forms[0]['label'] = 'foobar'
    resp.forms[0]['type'] = 'Text (line)'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/fields/'
    resp = resp.follow()
    assert 'foobar' in resp.text
    assert 'Use drag and drop' in resp.text

    assert len(FormDef.get(1).fields) == 1
    assert FormDef.get(1).fields[0].key == 'string'
    assert FormDef.get(1).fields[0].label == 'foobar'

    # add a title too
    resp.forms[0]['label'] = 'baz'
    resp.forms[0]['type'] = 'Title'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/fields/'
    resp = resp.follow()

    # check it's in the preview
    resp = app.get('/backoffice/forms/1/')
    assert '<h3 data-field-id="1">baz</h3>' in resp.text

def test_form_delete_field(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [fields.StringField(id='1', label='1st field', type='string')]
    formdef.store()
    formdef.data_class().wipe()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='fields/')
    assert '1st field' in resp.text
    assert 'Use drag and drop' in resp.text

    resp = resp.click(href='1/delete')
    assert 'You are about to remove the "1st field" field.' in resp.text
    assert 'Warning:' not in resp.text
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/fields/'
    resp = resp.follow()
    assert len(FormDef.get(1).fields) == 0

def test_form_delete_field_existing_data(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [
        fields.StringField(id='1', label='1st field', type='string'),
        fields.CommentField(id='2', label='comment field', type='comment'),
    ]
    formdef.store()
    formdef.data_class().wipe()

    formdata = formdef.data_class()()
    formdata.just_created()
    formdata.data = {'1': 'hello'}
    formdata.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='fields/')
    resp = resp.click(href='1/delete')
    assert 'You are about to remove the "1st field" field.' in resp.text
    assert 'Warning:' in resp.text
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/fields/#itemId_2'
    resp = resp.follow()
    assert len(FormDef.get(1).fields) == 1

    # check non-data fields do not show this warning
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='fields/')
    resp = resp.click(href='2/delete')
    assert 'You are about to remove the "comment field" field.' in resp.text
    assert 'Warning:' not in resp.text
    resp = resp.forms[0].submit()
    resp = resp.follow()
    assert len(FormDef.get(1).fields) == 0
 
def test_form_duplicate_field(pub):
    user = create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [fields.StringField(id='1', label='1st field', type='string')]
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='fields/')
    assert '1st field' in resp.text

    resp = resp.click(href='1/duplicate')
    assert resp.location == 'http://example.net/backoffice/forms/1/fields/#itemId_2'
    resp = resp.follow()
    assert len(FormDef.get(1).fields) == 2
    assert FormDef.get(1).fields[0].label == '1st field'
    assert FormDef.get(1).fields[1].label == '1st field'

def test_form_duplicate_file_field(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='fields/')

    # add a first field
    resp.forms[0]['label'] = 'foobar'
    resp.forms[0]['type'] = 'File Upload'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/%s/fields/' % formdef.id
    resp = resp.follow()
    assert 'foobar' in resp.text

    resp = resp.click(href='%s/duplicate' % FormDef.get(formdef.id).fields[0].id)
    assert resp.location == 'http://example.net/backoffice/forms/1/fields/#itemId_2'
    resp = resp.follow()

def test_form_edit_field(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [fields.StringField(id='1', label='1st field', type='string')]
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='fields/')
    assert '1st field' in resp.text

    resp = resp.click('Edit', href='1/')
    assert resp.forms[0]['label'].value == '1st field'
    resp.forms[0]['label'] = 'changed field'
    resp.forms[0]['required'] = False
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/forms/1/fields/#itemId_1'

    assert FormDef.get(1).fields[0].label == 'changed field'
    assert FormDef.get(1).fields[0].required == False

def test_form_edit_field_advanced(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [fields.StringField(id='1', label='1st field', type='string')]
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='fields/')
    assert '1st field' in resp.text

    resp = resp.click('Edit', href='1/')
    assert resp.forms[0]['label'].value == '1st field'
    assert '<legend>Additional parameters</legend>' in resp.text
    assert '<label for="form_prefill">Prefill</label>' in resp.text
    # check the "prefill" field is under additional parameters
    assert resp.text.index('<legend>Additional parameters</legend>') < \
            resp.text.index('<label for="form_prefill">Prefill</label>')

    # complete the "prefill" field
    resp.forms[0]['prefill$type'] = 'String / Template'
    resp.forms[0]['prefill$value_string'] = 'test'
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/forms/1/fields/#itemId_1'
    resp = resp.follow()

    assert FormDef.get(formdef.id).fields[0].prefill == {'type': 'string', 'value': 'test'}

    # do the same with 'data sources' field
    resp = resp.click('Edit', href='1/')
    assert resp.forms[0]['label'].value == '1st field'
    assert '<legend>Additional parameters</legend>' in resp.text
    assert '<label for="form_data_source">Data Source</label>' in resp.text
    # check the "data source" field is under additional parameters
    assert resp.text.index('<legend>Additional parameters</legend>') < \
            resp.text.index('<label for="form_data_source">Data Source</label>')

    # start filling the "data source" field
    resp.forms[0]['data_source$type'] = 'json'
    resp.forms[0]['data_source$value'] = 'http://example.net'
    resp = resp.forms[0].submit('submit')
    resp = resp.follow()

    # it should now appear before the additional parameters section
    resp = resp.click('Edit', href='1/')
    assert resp.text.index('<legend>Additional parameters</legend>') > \
            resp.text.index('<label for="form_data_source">Data Source</label>')

def test_form_prefill_field(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [fields.StringField(id='1', label='1st field', type='string')]
    formdef.store()

    app = login(get_app(pub))

    resp = app.get('/backoffice/forms/1/fields/1/')
    resp.form['prefill$type'] = 'String / Template'
    resp.form['prefill$value_string'] = 'test'
    resp = resp.form.submit('submit').follow()
    assert FormDef.get(formdef.id).fields[0].prefill == {'type': 'string', 'value': 'test'}

    resp = app.get('/backoffice/forms/1/fields/1/')
    resp.form['prefill$type'] = 'Python Expression'
    resp.form['prefill$value_formula'] = 'True'
    resp = resp.form.submit('submit').follow()
    assert FormDef.get(formdef.id).fields[0].prefill == {'type': 'formula', 'value': 'True'}

    resp = app.get('/backoffice/forms/1/fields/1/')
    resp.form['prefill$type'] = 'String / Template'
    resp.form['prefill$value_string'] = '{{form_var_toto}}'
    resp = resp.form.submit('submit').follow()
    assert FormDef.get(formdef.id).fields[0].prefill == {'type': 'string', 'value': '{{form_var_toto}}'}

    # check error handling
    resp = app.get('/backoffice/forms/1/fields/1/')
    resp.form['prefill$type'] = 'Python Expression'
    resp.form['prefill$value_formula'] = ':'
    resp = resp.form.submit('submit')
    assert 'invalid expression: unexpected EOF while parsing' in resp.text

    resp = app.get('/backoffice/forms/1/fields/1/')
    resp.form['prefill$type'] = 'String / Template'
    resp.form['prefill$value_string'] = '{% if %}'
    resp = resp.form.submit('submit')
    assert 'syntax error in Django template: Unexpected end of expression' in resp.text

def test_form_edit_string_field_validation(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [fields.StringField(id='1', label='1st field', type='string')]
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='fields/')
    assert '1st field' in resp.text

    resp = resp.click('Edit', href='1/')
    resp.form['validation$type'] = 'Regular Expression'
    resp.form['validation$value_regex'] = r'\d+'
    resp = resp.form.submit('submit').follow()
    assert FormDef.get(formdef.id).fields[0].validation == {'type': 'regex', 'value': r'\d+'}

    resp = resp.click('Edit', href='1/')
    resp.form['validation$type'] = 'None'
    resp = resp.form.submit('submit').follow()
    assert FormDef.get(formdef.id).fields[0].validation is None

    resp = resp.click('Edit', href='1/')
    resp.form['validation$type'] = 'Django Condition'
    resp.form['validation$value_django'] = 'value|decimal < 20'
    resp = resp.form.submit('submit').follow()
    assert FormDef.get(formdef.id).fields[0].validation == {'type': 'django', 'value': 'value|decimal < 20'}

    resp = resp.click('Edit', href='1/')
    resp.form['validation$type'] = 'Django Condition'
    resp.form['validation$value_django'] = '{{ value|decimal < 20 }}'
    resp = resp.form.submit('submit')
    assert 'syntax error' in resp.text

def test_form_edit_item_field(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [fields.ItemField(id='1', label='1st field', type='item')]
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='fields/')
    assert '1st field' in resp.text

    resp = resp.click('Edit', href='1/')
    assert resp.forms[0]['label'].value == '1st field'
    resp.forms[0]['label'] = 'changed field'
    resp.forms[0]['required'] = False
    resp = resp.forms[0].submit('items$add_element')
    # this adds a second field
    assert 'items$element0' in resp.form.fields
    assert 'items$element1' in resp.form.fields
    # but don't fill anything
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/forms/1/fields/#itemId_1'
    resp = resp.follow()

    assert FormDef.get(1).fields[0].label == 'changed field'
    assert FormDef.get(1).fields[0].required == False
    assert FormDef.get(1).fields[0].items is None

    # edit and fill with one item
    resp = resp.click('Edit', href='1/')
    assert resp.forms[0]['label'].value == 'changed field'
    resp.forms[0]['items$element0'] = 'XXX'
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/forms/1/fields/#itemId_1'
    assert FormDef.get(1).fields[0].items == ['XXX']

def test_form_edit_item_field_data_source(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [fields.ItemField(id='1', label='1st field', type='item')]
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/fields/1/')
    assert resp.form['data_source$type'].options == [
        (u'None', True, u'None'),
        (u'json', False, u'JSON URL'),
        (u'jsonp', False, u'JSONP URL'),
        (u'python', False, u'Python Expression')
    ]
    resp = resp.form.submit('submit').follow()

    data_source = NamedDataSource(name='Foobar')
    data_source.data_source = {'type': 'formula', 'value': '[]'}
    data_source.store()

    resp = app.get('/backoffice/forms/1/fields/1/')
    assert resp.form['data_source$type'].options == [
        (u'None', True, u'None'),
        (u'foobar', False, u'Foobar'),
        (u'json', False, u'JSON URL'),
        (u'jsonp', False, u'JSONP URL'),
        (u'python', False, u'Python Expression')
    ]
    resp.form['data_source$type'].value = 'foobar'
    resp = resp.form.submit('submit').follow()
    assert FormDef.get(formdef.id).fields[0].data_source == {'type': 'foobar'}

    carddef = CardDef()
    carddef.name = 'Baz'
    carddef.store()

    resp = app.get('/backoffice/forms/1/fields/1/')
    assert resp.form['data_source$type'].options == [
        (u'None', False, u'None'),
        (u'foobar', True, u'Foobar'),
        (u'json', False, u'JSON URL'),
        (u'jsonp', False, u'JSONP URL'),
        (u'python', False, u'Python Expression')
    ]

    carddef.digest_template = 'plop'
    carddef.store()

    resp = app.get('/backoffice/forms/1/fields/1/')
    assert resp.form['data_source$type'].options == [
        (u'None', False, u'None'),
        (u'carddef:%s' % carddef.url_name, False, u'Baz'),
        (u'foobar', True, u'Foobar'),
        (u'json', False, u'JSON URL'),
        (u'jsonp', False, u'JSONP URL'),
        (u'python', False, u'Python Expression')
    ]

    resp.form['data_source$type'].value = 'carddef:%s' % carddef.url_name
    resp = resp.form.submit('submit').follow()
    assert FormDef.get(formdef.id).fields[0].data_source == {'type': 'carddef:%s' % carddef.url_name}

    # set json source then back to none
    resp = app.get('/backoffice/forms/1/fields/1/')
    resp.form['data_source$type'].value = 'json'
    resp.form['data_source$value'].value = 'http://whatever'
    resp = resp.form.submit('submit').follow()
    assert FormDef.get(formdef.id).fields[0].data_source == {'type': 'json', 'value': 'http://whatever'}

    resp = app.get('/backoffice/forms/1/fields/1/')
    resp.form['data_source$type'].value = 'None'
    resp = resp.form.submit('submit').follow()
    resp = app.get('/backoffice/forms/1/')

def test_form_edit_items_field(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [fields.ItemsField(id='1', label='1st field', type='items')]
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='fields/')
    assert '1st field' in resp.text

    resp = resp.click('Edit', href='1/')
    assert resp.forms[0]['label'].value == '1st field'
    resp.forms[0]['label'] = 'changed field'
    resp.forms[0]['required'] = False
    resp = resp.forms[0].submit('items$add_element')
    # this adds a second field
    assert 'items$element0' in resp.form.fields
    assert 'items$element1' in resp.form.fields
    # but don't fill anything
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/forms/1/fields/#itemId_1'
    resp = resp.follow()

    assert FormDef.get(1).fields[0].label == 'changed field'
    assert FormDef.get(1).fields[0].required == False
    assert FormDef.get(1).fields[0].items is None

    # edit and fill with one item
    resp = resp.click('Edit', href='1/')
    assert resp.forms[0]['label'].value == 'changed field'
    resp.forms[0]['items$element0'] = 'XXX'
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/forms/1/fields/#itemId_1'
    assert FormDef.get(1).fields[0].items == ['XXX']

    # check prefilling is only possible with Python
    resp = resp.follow()
    resp = resp.click('Edit', href='1/')
    assert resp.forms[0]['prefill$type'].options == [
            (u'None', True, u'None'), (u'Python Expression', False, u'Python Expression')]

def test_form_edit_page_field(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='fields/')
    assert 'There are not yet any fields for this form' in resp.text

    resp.forms[0]['label'] = 'foobar'
    resp.forms[0]['type'] = 'Page'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/fields/'
    resp = resp.follow()
    assert 'Page #1' in resp.text
    assert 'foobar' in resp.text
    assert 'Use drag and drop' in resp.text

    assert len(FormDef.get(1).fields) == 1
    assert FormDef.get(1).fields[0].key == 'page'
    assert FormDef.get(1).fields[0].label == 'foobar'

    resp = resp.click('Edit', href='1/')
    resp.form['post_conditions$element0$condition$type'] = 'python'
    resp.form['post_conditions$element0$condition$value_python'] = 'foo'
    resp.form['post_conditions$element0$error_message'] = 'bar'
    resp = resp.form.submit('post_conditions$add_element')
    resp.form['post_conditions$element1$condition$type'] = 'python'
    resp.form['post_conditions$element1$condition$value_python'] = 'foo2'
    resp = resp.form.submit('submit')
    assert 'Both condition and error message are required.' in resp.text
    resp.form['post_conditions$element1$error_message'] = 'bar2'
    resp = resp.form.submit('submit').follow()

    assert FormDef.get(1).fields[0].post_conditions == [
            {'condition': {'type': 'python', 'value': 'foo'}, 'error_message': 'bar'},
            {'condition': {'type': 'python', 'value': 'foo2'}, 'error_message': 'bar2'},
    ]

    resp = resp.click('Edit', href='1/')
    resp.form['post_conditions$element1$condition$type'] = 'django'
    resp.form['post_conditions$element1$condition$value_django'] = 'foo3'
    resp = resp.form.submit('submit').follow()
    assert FormDef.get(1).fields[0].post_conditions == [
            {'condition': {'type': 'python', 'value': 'foo'}, 'error_message': 'bar'},
            {'condition': {'type': 'django', 'value': 'foo3'}, 'error_message': 'bar2'},
    ]

def test_form_edit_comment_field(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [fields.CommentField(id='1', label='a comment field', type='comment')]
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/fields/1/')
    assert 'a comment field' in resp.text
    assert 'WysiwygTextWidget' in resp.text

    # legacy, double line breaks will be converted to paragraphs
    formdef.fields = [fields.CommentField(id='1', type='comment',
        label='a comment field\n\na second line')]
    formdef.store()
    resp = app.get('/backoffice/forms/1/fields/1/')
    assert 'WysiwygTextWidget' in resp.text
    resp = resp.form.submit('submit')
    assert FormDef.get(formdef.id).fields[0].label == '<p>a comment field</p>\n<p>a second line</p>'

    # starting with a <
    formdef.fields = [fields.CommentField(id='1', type='comment',
        label='<strong>a comment field\n\na second line</strong>')]
    formdef.store()
    resp = app.get('/backoffice/forms/1/fields/1/')
    assert 'WysiwygTextWidget' in resp.text

    # legacy, ezt syntax in a non-html field will be presented as a textarea
    formdef.fields = [fields.CommentField(id='1', type='comment',
        label='[if-any toto]hello world[end]')]
    formdef.store()
    resp = app.get('/backoffice/forms/1/fields/1/')
    assert 'WysiwygTextWidget' not in resp.text

    # check a new field is created with label as HTML, enclosing label in <p>
    resp = app.get('/backoffice/forms/1/fields/')
    resp.forms[0]['label'] = 'foobar'
    resp.forms[0]['type'] = 'Comment'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/fields/'
    assert FormDef.get(formdef.id).fields[-1].label == '<p>foobar</p>'

    # unless label is already given as HTML
    resp = app.get('/backoffice/forms/1/fields/')
    resp.forms[0]['label'] = '<div>blah</div>'
    resp.forms[0]['type'] = 'Comment'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/1/fields/'
    assert FormDef.get(formdef.id).fields[-1].label == '<div>blah</div>'

def test_form_edit_map_field(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [fields.MapField(id='1', label='a field', type='map')]
    formdef.store()

    app = login(get_app(pub))

    resp = app.get('/backoffice/forms/1/fields/1/')
    resp = resp.form.submit('submit')
    assert resp.location

    # min
    formdef.store()
    resp = app.get('/backoffice/forms/1/fields/1/')
    resp.form['min_zoom'] = 'Wide area'
    resp = resp.form.submit('submit')
    assert FormDef.get(formdef.id).fields[0].min_zoom == '9'

    # max
    formdef.store()
    resp = app.get('/backoffice/forms/1/fields/1/')
    resp.form['max_zoom'] = 'Small road'
    resp = resp.form.submit('submit')
    assert FormDef.get(formdef.id).fields[0].max_zoom == '16'

    # both
    formdef.store()
    resp = app.get('/backoffice/forms/1/fields/1/')
    resp.form['min_zoom'] = 'Wide area'
    resp.form['max_zoom'] = 'Small road'
    resp = resp.form.submit('submit')
    assert FormDef.get(formdef.id).fields[0].min_zoom == '9'
    assert FormDef.get(formdef.id).fields[0].max_zoom == '16'

    # inverted
    formdef.store()
    resp = app.get('/backoffice/forms/1/fields/1/')
    resp.form['min_zoom'] = 'Small road'
    resp.form['max_zoom'] = 'Wide area'
    resp = resp.form.submit('submit')
    assert 'widget-with-error' in resp.text

    # initial out of range
    formdef.store()
    resp = app.get('/backoffice/forms/1/fields/1/')
    resp.form['initial_zoom'] = 'Whole world'
    resp.form['min_zoom'] = 'Wide area'
    resp.form['max_zoom'] = 'Small road'
    resp = resp.form.submit('submit')
    assert 'widget-with-error' in resp.text

    # prefill fields
    resp = app.get('/backoffice/forms/1/fields/1/')
    resp.form['prefill$type'].value = 'Geolocation'
    resp.form['prefill$value_geolocation'].value = 'Position'
    resp = resp.form.submit('submit')
    assert FormDef.get(formdef.id).fields[0].prefill == {
            'type': 'geolocation', 'value': 'position'}

def test_form_edit_field_warnings(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [fields.StringField(id='%d' % i, label='field %d' % i, type='string')
                      for i in range(1,10)]
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='fields/')
    assert 'more than 500 fields' not in resp.text
    assert 'first field should be of type "page"' not in resp.text

    formdef.fields.append(fields.PageField(id='1000', label='page', type='page'))
    formdef.store()
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='fields/')
    assert 'more than 500 fields' not in resp.text
    assert 'first field should be of type "page"' in resp.text

    formdef.fields.extend([fields.StringField(id='%d' % i, label='field %d' % i, type='string')
                      for i in range(10,510)])
    formdef.store()
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='fields/')
    assert 'more than 500 fields' in resp.text
    assert 'first field should be of type "page"' in resp.text

    FormDef.wipe()

def test_form_limit_display_to_page(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [fields.PageField(id='0', label='1st page', type='page'),
            fields.StringField(id='1', label='string', type='string', varname='foobar'),
            fields.PageField(id='2', label='2nd page', type='page'),
            fields.StringField(id='3', label='string 2', type='string', varname='baz')]
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/fields/')
    assert '{{form_var_foobar}}' in resp.text
    assert '2nd page' in resp.text
    resp = resp.click('Limit display to this page', index=0)
    hidden_fields = ''.join(re.findall('display:none.*', resp.text))
    assert 'Display all pages' in resp.text
    assert '{{form_var_foobar}}' not in hidden_fields
    assert '{{form_var_baz}}' in hidden_fields

def test_form_legacy_int_id(pub):
    create_superuser(pub)
    create_role()

    Category.wipe()
    cat = Category(name='Foo')
    cat.store()

    Workflow.wipe()
    workflow = Workflow(name='Workflow One')
    # create it with a single status
    workflow.possible_status = [Workflow.get_default_workflow().possible_status[-1]]
    workflow.store()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []

    role = Role(name='ZAB') # Z to get sorted last
    role.store()

    # set attributes using integers
    formdef.category_id = int(cat.id)
    formdef.workflow_id = int(workflow.id)
    formdef.workflow_roles = {'_receiver': int(role.id)}
    formdef.roles = ['logged-users', int(role.id)]

    formdef.store()

    formdef = FormDef.get(formdef.id) # will run migrate

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')

    resp = resp.click(href='category')
    assert resp.forms[0]['category_id'].value

    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='workflow', index=1)
    assert resp.forms[0]['workflow_id'].value

    resp = app.get('/backoffice/forms/1/')
    resp = resp.click('User Roles')
    assert resp.forms[0]['roles$element0'].value == 'logged-users'
    assert resp.forms[0]['roles$element1'].value == role.id

    resp = app.get('/backoffice/forms/1/')
    resp = resp.click('Recipient')
    assert resp.forms[0]['role_id'].value == role.id

def test_form_anonymise(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    data_class = formdef.data_class()
    data_class.wipe()
    for i in range(10):
        formdata = data_class()
        formdata.just_created()
        if i < 3:
            formdata.status = 'wf-new'
        else:
            formdata.status = 'wf-rejected'
        formdata.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='anonymise')
    resp = resp.form.submit('cancel')
    assert len([x for x in formdef.data_class().select() if x.anonymised]) == 0
    assert resp.location == 'http://example.net/backoffice/forms/1/'

    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='anonymise')
    resp = resp.form.submit('submit')
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    assert len([x for x in formdef.data_class().select() if x.anonymised]) == 0

    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='anonymise')
    resp.form['before_request_date'].value = (datetime.datetime.today() +
            datetime.timedelta(days=1)).strftime('%Y-%m-%d')
    resp = resp.form.submit('submit')
    assert resp.location == 'http://example.net/backoffice/forms/1/'
    assert len([x for x in formdef.data_class().select() if x.anonymised]) == 7

    data_class.wipe()
    for i in range(110):
        formdata = data_class()
        formdata.just_created()
        if i < 3:
            formdata.status = 'wf-rejected'
        else:
            formdata.status = 'wf-finished'
        formdata.store()

    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='anonymise')
    resp.form['before_request_date'].value = (datetime.datetime.today() +
            datetime.timedelta(days=1)).strftime('%Y-%m-%d')
    resp.form['endpoints$elementfinished'].checked = False
    resp = resp.form.submit('submit')
    assert '?job=' in resp.location
    resp = resp.follow()
    assert 'job-status' in resp.text
    assert 'completed' in resp.text
    assert len([x for x in formdef.data_class().select() if x.anonymised]) == 3

def test_form_public_url(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click('Display public URL')
    assert 'http://example.net/form-title/' in resp.text

def test_form_archive(pub):
    create_superuser(pub)
    create_role()

    if pub.is_using_postgresql():
        # this doesn't exist in SQL
        pytest.skip('no archive in SQL mode')
        return

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    data_class = formdef.data_class()
    data_class.wipe()
    for i in range(10):
        formdata = data_class()
        formdata.just_created()
        if i < 3:
            formdata.status = 'wf-new'
        else:
            formdata.status = 'wf-rejected'
        formdata.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='archive')
    resp = resp.form.submit('cancel')
    assert data_class.count() == 10
    assert resp.location == 'http://example.net/backoffice/forms/1/'

    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='archive')
    resp = resp.form.submit('submit')
    assert resp.content_type == 'application/x-wcs-archive'
    tf = tarfile.open(fileobj=BytesIO(resp.body))
    assert 'formdef' in [x.name for x in tf.getmembers()]
    assert len(tf.getmembers()) == 8 # 7 formdata + 1 formdef

    # second archive, it shouldn't get anything (but the formdef)
    resp = app.get('/backoffice/forms/1/')
    resp = resp.click(href='archive')
    resp = resp.form.submit('submit')
    assert resp.content_type == 'application/x-wcs-archive'
    tf = tarfile.open(fileobj=BytesIO(resp.body))
    assert 'formdef' in [x.name for x in tf.getmembers()]
    assert len(tf.getmembers()) == 1 # 0 formdata + 1 formdef

def test_form_overwrite(pub):
    user = create_superuser(pub)
    role = create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form test'
    formdef.table_name = 'xxx'
    formdef.fields = [fields.StringField(id='1', label='1st field', type='string'),
            fields.StringField(id='2', label='2nd field', type='string')]
    formdef.store()

    formdata = formdef.data_class()()
    formdata.data = {'1': 'foo', '2': 'bar'}
    formdata.just_created()
    formdata.store()

    formdef_id = formdef.id
    formdef.fields[0].label = '1st modified field'
    formdef_xml = ET.tostring(formdef.export_to_xml(include_id=True))

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/%s/' % formdef_id)
    resp = resp.click(href='overwrite')
    resp.forms[0]['file'] = Upload('formdef.wcs', formdef_xml)
    resp = resp.forms[0].submit()
    assert 'Overwrite - Summary of changes' in resp.text
    resp = resp.forms[0].submit()
    assert FormDef.get(formdef_id).fields[0].label == '1st modified field'
    resp = resp.follow()
    assert 'The form has been successfully overwritten.' in resp.text

    # check with added/removed field
    new_formdef = FormDef()
    new_formdef.name = 'form test overwrite'
    new_formdef.fields = [
            fields.StringField(id='2', label='2nd field', type='string'),
            fields.StringField(id='3', label='3rd field', type='string')]
    new_formdef_xml = ET.tostring(new_formdef.export_to_xml(include_id=True))

    # and no data within
    formdef.data_class().wipe()
    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/%s/' % formdef_id)
    resp = resp.click(href='overwrite')
    resp.forms[0]['file'] = Upload('formdef.wcs', new_formdef_xml)
    resp = resp.forms[0].submit()
    assert FormDef.get(formdef_id).fields[0].id == '2'
    assert FormDef.get(formdef_id).fields[0].label == '2nd field'
    assert FormDef.get(formdef_id).fields[1].id == '3'
    assert FormDef.get(formdef_id).fields[1].label == '3rd field'

    # and data within
    formdef.store()
    formdata.data = {'1': 'foo', '2': 'bar'}
    formdata.just_created()
    formdata.store()

    resp = app.get('/backoffice/forms/%s/' % formdef_id)
    resp = resp.click(href='overwrite')
    resp.forms[0]['file'] = Upload('formdef.wcs', new_formdef_xml)
    resp = resp.forms[0].submit()
    assert 'The form removes or changes fields' in resp.text
    assert resp.forms[0]['force'].checked is False
    resp = resp.forms[0].submit()  # without checkbox (back to same form)
    resp.forms[0]['force'].checked = True
    resp = resp.forms[0].submit()

    assert FormDef.get(formdef_id).fields[0].id == '2'
    assert FormDef.get(formdef_id).fields[0].label == '2nd field'
    assert FormDef.get(formdef_id).fields[1].id == '3'
    assert FormDef.get(formdef_id).fields[1].label == '3rd field'

    # check with a field of different type
    formdef = FormDef.get(formdef_id)
    formdata = formdef.data_class()()
    formdata.data = {'1': 'foo', '2': 'bar', '3': 'baz'}
    formdata.just_created()
    formdata.store()

    new_formdef = FormDef()
    new_formdef.name = 'form test overwrite'
    new_formdef.fields = [
            fields.StringField(id='2', label='2nd field', type='string'),
            fields.DateField(id='3', label='3rd field, date', type='date')]  # (string -> date)
    new_formdef_xml = ET.tostring(new_formdef.export_to_xml(include_id=True))

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/%s/' % formdef_id)
    resp = resp.click(href='overwrite', index=0)
    resp.forms[0]['file'] = Upload('formdef.wcs', new_formdef_xml)
    resp = resp.forms[0].submit()
    assert 'The form removes or changes fields' in resp.text
    resp.forms[0]['force'].checked = True
    resp = resp.forms[0].submit()
    assert FormDef.get(formdef_id).fields[1].id == '3'
    assert FormDef.get(formdef_id).fields[1].label == '3rd field, date'
    assert FormDef.get(formdef_id).fields[1].type == 'date'

    # check we kept stable references
    assert FormDef.get(formdef_id).url_name == 'form-test'
    assert FormDef.get(formdef_id).table_name == 'xxx'

    # check existing data
    data = FormDef.get(formdef_id).data_class().get(formdata.id).data
    assert data.get('2') == 'bar'
    if pub.is_using_postgresql():
        # in SQL, check data with different type has been removed
        assert data.get('3') is None


def test_form_export_import_export_overwrite(pub):
    create_superuser(pub)
    create_role()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.table_name = 'xxx'
    formdef.fields = [  # unordered id
        fields.StringField(id='1', label='field 1', type='string'),
        fields.DateField(id='12', label='field 2', type='date'),
        fields.ItemField(id='4', label='field 3', type='item'),
    ]
    formdef.store()

    # add data
    formdata = formdef.data_class()()
    formdata.data = {'1': 'foo'}
    formdata.just_created()
    formdata.store()

    formdef_xml = ET.tostring(formdef.export_to_xml(include_id=True))

    assert FormDef.count() == 1
    assert formdef.url_name == 'form-title'

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/')
    resp = resp.click(href='import')
    resp.forms[0]['file'] = Upload('formdef.wcs', formdef_xml)
    resp = resp.forms[0].submit()
    assert FormDef.count() == 2

    formdef2 = FormDef.get(2)
    assert formdef2.url_name == 'form-title-1'
    # fields are imported with original ids
    for i, field in enumerate(formdef.fields):
        field2 = formdef2.fields[i]
        assert (field.id, field.label, field.type) == (field2.id, field2.label, field2.type)

    # modify imported formdef, then overwrite original formdef with it
    formdef2.fields.insert(2, fields.StringField(id='2', label='field 4', type='string'))
    formdef2.fields.insert(3, fields.DateField(id='3', label='field 5', type='date'))
    formdef2.fields.append(fields.ItemField(id='5', label='field 6', type='item'))
    formdef2.store()
    formdef2_xml = ET.tostring(formdef2.export_to_xml(include_id=True))
    resp = app.get('/backoffice/forms/%s/' % formdef.id)
    resp = resp.click(href='overwrite')
    resp.forms[0]['file'] = Upload('formdef.wcs', formdef2_xml)
    resp = resp.forms[0].submit()
    assert 'Overwrite - Summary of changes' in resp.text
    resp = resp.forms[0].submit()
    formdef_overwrited = FormDef.get(formdef.id)
    for i, field in enumerate(formdef2.fields):
        field_ow = formdef_overwrited.fields[i]
        assert (field.id, field.label, field.type) == (field_ow.id, field_ow.label, field_ow.type)

def test_form_comment_with_error_in_wscall(http_requests, pub):
    create_superuser(pub)
    NamedWsCall.wipe()

    wscall = NamedWsCall(name='xxx')
    wscall.description = 'description'
    wscall.request = {
            'url': 'http://remote.example.net/404',
            'request_signature_key': 'xxx',
            'qs_data': {'a': 'b'},
            'method': 'POST',
            'post_data': {'c': 'd'},
    }
    wscall.store()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [fields.CommentField(id='1',
        label='x [webservice.xxx.foobar] x', type='comment')]
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/%s/' % formdef.id)
    assert 'x [webservice.xxx.foobar] x' in resp.text

def test_workflows(pub):
    create_superuser(pub)
    app = login(get_app(pub))
    app.get('/backoffice/workflows/')

def test_workflows_default(pub):
    create_superuser(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/')
    assert 'Default' in resp.text
    resp = resp.click(href='_default')
    assert 'Just Submitted' in resp.text
    assert 'This is the default workflow' in resp.text
    # makes sure it cannot be edited
    assert 'Edit' not in resp.text

    # and makes sure status are not editable either
    resp = resp.click('Just Submitted')
    assert '<h2>Just Submitted' in resp.text
    assert 'Change Status Name' not in resp.text
    assert 'Delete' not in resp.text

def test_workflows_new(pub):
    user = create_superuser(pub)
    role = create_role()
    Workflow.wipe()
    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/')

    # create a new workflow
    resp = resp.click('New Workflow')
    resp.forms[0]['name'] = 'a new workflow'
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/workflows/1/'
    resp = resp.follow()
    assert 'There are not yet any status defined in this workflow' in resp.text
    assert not '<svg ' in resp.text

    # create a new status
    resp.forms[0]['name'] = 'new status'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/workflows/1/'
    resp = resp.follow()
    assert '<svg ' in resp.text
    assert not '@import ' in resp.text

    # create a new action
    resp = resp.click('new status')
    resp.forms[0]['action-interaction'] = 'Alert'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/workflows/1/status/1/'
    resp = resp.follow()
    assert 'Use drag and drop' in resp.text

    # fill action
    resp = resp.click('Alert')
    resp.forms[0]['message'] = 'bla bla bla'
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/workflows/1/status/1/items/'
    resp = resp.follow()
    assert resp.location == 'http://example.net/backoffice/workflows/1/status/1/'

    wf = Workflow.get(1)
    assert wf.name == 'a new workflow'
    assert wf.possible_status[0].name == 'new status'
    assert wf.possible_status[0].items[0].message == 'bla bla bla'

def test_workflows_svg(pub):
    user = create_superuser(pub)
    role = create_role()
    Workflow.wipe()

    workflow = Workflow(name='foo')
    st1 = workflow.add_status(name='baz')
    commentable = CommentableWorkflowStatusItem()
    commentable.id = '_commentable'
    commentable.by = [role.id]
    commentable.label = 'foobar'
    st1.items.append(commentable)
    commentable.parent = st1
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/%s/svg' % workflow.id)
    assert resp.content_type == 'image/svg+xml'
    assert '/static/css/dc2/admin.css' in resp.text

def test_workflows_edit(pub):
    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click(href='edit')
    assert resp.forms[0]['name'].value == 'foo'
    resp.forms[0]['name'] = 'baz'
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/workflows/1/'
    resp = resp.follow()
    assert 'baz' in resp.text

def test_workflows_edit_status(pub):
    create_superuser(pub)
    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.add_status(name='baz')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('baz')

    resp = resp.click('Change Status Name')
    resp.forms[0]['name'] = 'bza'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/workflows/1/status/1/'
    resp = resp.follow()
    assert Workflow.get(1).possible_status[0].name == 'bza'

    resp = resp.click('Change Display Settings')
    resp.forms[0]['hide_status_from_user'].checked = True
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/workflows/1/status/1/'
    resp = resp.follow()
    assert Workflow.get(1).possible_status[0].visibility == ['_receiver']

    resp = resp.click('Change Terminal Status')
    resp.forms[0]['force_terminal_status'].checked = True
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/workflows/1/status/1/'
    resp = resp.follow()
    assert Workflow.get(1).possible_status[0].forced_endpoint == True

    resp = resp.click('Change Display Settings')
    assert resp.forms[0]['colour'].value == 'FFFFFF'
    assert resp.forms[0]['extra_css_class'].value == ''
    resp.forms[0]['colour'] = 'FF0000'
    resp.forms[0]['extra_css_class'] = 'plop'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/workflows/1/status/1/'
    resp = resp.follow()
    assert Workflow.get(1).possible_status[0].colour == 'FF0000'
    assert Workflow.get(1).possible_status[0].extra_css_class == 'plop'

    resp = resp.click('Change Display Settings')
    assert resp.forms[0]['colour'].value == 'FF0000'
    assert resp.forms[0]['extra_css_class'].value == 'plop'
    resp.forms[0]['extra_css_class'] = 'xxx'
    resp = resp.forms[0].submit('cancel')
    assert Workflow.get(1).possible_status[0].extra_css_class == 'plop'
    assert resp.location == 'http://example.net/backoffice/workflows/1/status/1/'
    resp = resp.follow()

    resp = resp.click('Change Backoffice Information Text')
    assert resp.forms[0]['backoffice_info_text'].value == ''
    resp.forms[0]['backoffice_info_text'] = '<p>Hello</p>'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/workflows/1/status/1/'
    resp = resp.follow()
    assert 'Hello' in Workflow.get(1).possible_status[0].backoffice_info_text

def test_workflows_delete_status(pub):
    create_superuser(pub)
    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.add_status(name='baz')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('baz')

    resp = resp.click('Delete')
    resp = resp.form.submit('cancel')
    assert resp.location == 'http://example.net/backoffice/workflows/1/status/1/'
    resp = resp.follow()

    resp = resp.click('Delete')
    resp = resp.form.submit('submit')
    assert resp.location == 'http://example.net/backoffice/workflows/1/'
    resp = resp.follow()

def test_workflows_delete(pub):
    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')

    resp = resp.click(href='delete')
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/workflows/'
    resp = resp.follow()
    assert Workflow.count() == 0

def test_workflows_export_import(pub):
    create_superuser(pub)

    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.add_status(name='baz')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('Export')
    assert resp.content_type == 'application/x-wcs-form'
    wf_export = resp.body

    resp = app.get('/backoffice/workflows/')
    resp = resp.click('Import')
    resp.form['file'] = Upload('xxx.wcs', wf_export)
    resp = resp.form.submit('submit')
    assert resp.location == 'http://example.net/backoffice/workflows/2/'
    resp = resp.follow()
    assert 'This workflow has been successfully imported' in resp.text
    assert Workflow.get(2).name == 'Copy of foo'

    resp = app.get('/backoffice/workflows/')
    resp = resp.click('Import')
    resp.form['file'] = Upload('xxx.wcs', b'garbage')
    resp = resp.form.submit('submit')
    assert 'Invalid File' in resp.text
    assert Workflow.count() == 2

def test_workflows_export_import_create_role(pub):
    create_superuser(pub)

    Role.wipe()
    role = Role()
    role.name = 'PLOP'
    role.store()

    Workflow.wipe()
    workflow = Workflow(name='foo')
    st1 = workflow.add_status(name='baz')
    commentable = CommentableWorkflowStatusItem()
    commentable.id = '_commentable'
    commentable.by = [role.id]
    st1.items.append(commentable)
    commentable.parent = st1
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('Export')
    assert resp.content_type == 'application/x-wcs-form'
    wf_export = resp.body

    resp = app.get('/backoffice/workflows/')
    resp = resp.click('Import')
    resp.form['file'] = Upload('xxx.wcs', wf_export)
    resp = resp.form.submit('submit')
    assert resp.location == 'http://example.net/backoffice/workflows/2/'
    resp = resp.follow()
    assert 'This workflow has been successfully imported' in resp.text
    assert Workflow.get(2).name == 'Copy of foo'
    assert Workflow.get(2).possible_status[0].items[0].by == [role.id]

    role.remove_self()

    # automatically create role
    resp = app.get('/backoffice/workflows/')
    resp = resp.click('Import')
    resp.form['file'] = Upload('xxx.wcs', wf_export)
    resp = resp.form.submit('submit')
    assert resp.location == 'http://example.net/backoffice/workflows/3/'
    resp = resp.follow()
    assert 'This workflow has been successfully imported' in resp.text
    assert Workflow.get(3).name == 'Copy of foo (2)'
    assert Role.count() == 1
    assert Role.select()[0].name == 'PLOP'
    assert Workflow.get(2).possible_status[0].items[0].by == [Role.select()[0].id]

    # don't create role if they are managed by the identity provider
    Role.wipe()

    pub.cfg['sp'] = {'idp-manage-roles': True}
    pub.write_cfg()
    resp = app.get('/backoffice/workflows/')
    resp = resp.click('Import')
    resp.form['file'] = Upload('xxx.wcs', wf_export)
    resp = resp.form.submit('submit')
    assert 'Invalid File (Unknown referenced role (PLOP))' in resp.text

def test_workflows_duplicate(pub):
    create_superuser(pub)

    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.add_status(name='baz')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('Duplicate')
    assert resp.location == 'http://example.net/backoffice/workflows/2/'
    resp = resp.follow()
    assert Workflow.get(2).name == 'foo (copy)'

    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('Duplicate')
    assert resp.location == 'http://example.net/backoffice/workflows/3/'
    resp = resp.follow()
    assert Workflow.get(3).name == 'foo (copy 2)'

def test_workflows_add_all_actions(pub):
    create_superuser(pub)

    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.add_status(name='baz')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('baz')

    for category in ('status-change', 'interaction', 'formdata-action', 'user-action'):
        for action in [x[0] for x in resp.forms[0]['action-%s' % category].options if x[0]]:
            resp.forms[0]['action-%s' % category] = action
            resp = resp.forms[0].submit()
            resp = resp.follow()

    i = 1
    for category in ('status-change', 'interaction', 'formdata-action', 'user-action'):
        for action in [x[0] for x in resp.forms[0]['action-%s' % category].options if x[0]]:
            resp = resp.click('Edit', href='items/%d/' % i, index=0)
            resp = resp.forms[0].submit('cancel')
            resp = resp.follow() # redirect to items/
            resp = resp.follow() # redirect to ./
            i += 1

def test_workflows_check_available_actions(pub):
    create_superuser(pub)

    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.add_status(name='baz')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('baz')

    assert not 'Criticality Levels' in [x[0] for x in resp.forms[0]['action-formdata-action'].options]
    assert not 'SMS' in [x[0] for x in resp.forms[0]['action-interaction'].options]
    assert not 'User Notification' in [x[0] for x in resp.forms[0]['action-interaction'].options]

    if not pub.site_options.has_section('variables'):
        pub.site_options.add_section('variables')
    pub.site_options.set('variables', 'portal_url', 'https://www.example.net/')
    with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
        pub.site_options.write(fd)

    pub.cfg['sms'] = {'mode': 'foobar'}
    pub.write_cfg()
    workflow.criticality_levels = [WorkflowCriticalityLevel(name='green')]
    workflow.store()
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('baz')
    assert 'Criticality Levels' in [x[0] for x in resp.forms[0]['action-formdata-action'].options]
    assert 'SMS' in [x[0] for x in resp.forms[0]['action-interaction'].options]
    assert 'User Notification' in [x[0] for x in resp.forms[0]['action-interaction'].options]

    for action in ('Criticality Levels', 'SMS', 'User Notification'):
        for category in ('status-change', 'interaction', 'formdata-action', 'user-action'):
            if action in [x[0] for x in resp.forms[0]['action-%s' % category].options if x[0]]:
                resp.forms[0]['action-%s' % category] = action
                resp = resp.forms[0].submit()
                resp = resp.follow()

    for i in range(3):
        resp = resp.click('Edit', href='items/%d/' % (i+1), index=0)
        resp = resp.forms[0].submit('cancel')
        resp = resp.follow() # redirect to items/
        resp = resp.follow() # redirect to ./


def test_workflows_edit_dispatch_action(pub):
    create_superuser(pub)
    role = create_role()
    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.add_status(name='baz')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('baz')

    resp.forms[0]['action-formdata-action'] = 'Function/Role Linking'
    resp = resp.forms[0].submit()
    resp = resp.follow()

    resp = resp.click('Function/Role Linking')
    resp.form['rules$element0$value'].value = 'FOOBAR'
    resp.form['rules$element0$role_id'].value = str(role.id)
    resp = resp.form.submit('submit')
    resp = resp.follow()
    resp = resp.follow()

    resp = resp.click('Function/Role Linking')
    assert resp.form['rules$element0$value'].value == 'FOOBAR'
    resp.form['rules$element1$value'].value = 'BARFOO'
    resp.form['rules$element1$role_id'].value = str(role.id)
    resp = resp.form.submit('submit')

    workflow = Workflow.get(workflow.id)
    assert workflow.possible_status[0].items[0].rules == [
            {'value': 'FOOBAR', 'role_id': '1'}, {'value': 'BARFOO', 'role_id': '1'}]

def test_workflows_edit_email_action(pub):
    create_superuser(pub)
    role = create_role()
    Workflow.wipe()
    workflow = Workflow(name='foo')
    st1 = workflow.add_status(name='baz')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('baz')

    resp.forms[0]['action-interaction'] = 'Email'
    resp = resp.forms[0].submit()
    resp = resp.follow()

    resp = resp.click('Email')
    item_url = resp.request.url

    ok_strings = [
        'Hello world', # ordinary string
        'Hello [world]', # unknown reference
        'Hello [if-any world][world][end]', # unknown reference, in condition
        'Hello world ][', # random brackets
    ]

    for field in ('body', 'subject'):
        for ok_string in ok_strings:
            resp = app.get(item_url)
            resp.form[field] = ok_string
            resp = resp.form.submit('submit')
            assert resp.location

        resp = app.get(item_url)
        resp.form[field] = 'Hello {% if world %}{{ world }}{% else %}.'
        resp = resp.form.submit('submit')
        assert 'syntax error in Django template' in resp.text and 'Unclosed tag' in resp.text

        resp = app.get(item_url)
        resp.form[field] = 'Hello {% if world %}{{ world }}{% else %}.{% endif %}{% endif %}'
        resp = resp.form.submit('submit')
        assert 'syntax error in Django template' in resp.text and 'Invalid block tag' in resp.text

        resp = app.get(item_url)
        resp.form[field] = 'Hello [if-any world][world][else].'
        resp = resp.form.submit('submit')
        assert 'syntax error in ezt template' in resp.text and 'unclosed block' in resp.text

        resp = app.get(item_url)
        resp.form[field] = 'Hello [if-any world][world][else].[end] [end]'
        resp = resp.form.submit('submit')
        assert 'syntax error in ezt template' in resp.text and 'unmatched [end]' in resp.text

    # attachments without backoffice fields: python expressions
    resp = app.get(item_url)
    assert "Attachments (Python expressions)" in resp.text
    resp.form['attachments$element0'] = 'form_var_upload_raw'
    resp = resp.form.submit('submit')
    assert resp.location
    resp = app.get(item_url)
    assert "Attachments (Python expressions)" in resp.text
    assert resp.form['attachments$element0'].value == 'form_var_upload_raw'
    sendmail = Workflow.get(workflow.id).get_status(st1.id).items[0]
    assert sendmail.attachments == ['form_var_upload_raw']

    # attachments with backoffice fields: select-with-other inputs
    workflow = Workflow.get(workflow.id)
    workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
    workflow.backoffice_fields_formdef.fields = [
        fields.FileField(id='bo1-1x', label='bo field 1', type='file', varname='upload'),
        fields.FileField(id='bo2-2x', label='bo field 2', type='file', varname='upload2'),
        fields.FileField(id='bo3-3x', label='bo field varnameless', type='file'),
    ]
    workflow.store()
    resp = app.get(item_url)
    assert "Attachments" in resp.text
    assert "Attachments (Python expressions)" not in resp.text
    assert resp.form['attachments$element0$choice'].value == 'form_var_upload_raw'
    assert len(resp.form['attachments$element0$choice'].options) == 5
    resp.form['attachments$element1$choice'] = 'form_var_upload2_raw'
    resp = resp.form.submit('submit')
    assert resp.location
    sendmail = Workflow.get(workflow.id).get_status(st1.id).items[0]
    assert sendmail.attachments == ['form_var_upload_raw', 'form_var_upload2_raw']

    resp = app.get(item_url)
    resp.form['attachments$element2$choice'] = 'form_fbo3_3x'
    resp = resp.form.submit('submit')
    assert resp.location
    sendmail = Workflow.get(workflow.id).get_status(st1.id).items[0]
    assert sendmail.attachments == ['form_var_upload_raw', 'form_var_upload2_raw', 'form_fbo3_3x']

    resp = app.get(item_url)
    resp.form['attachments$element3$choice'] = '__other'
    resp.form['attachments$element3$other'] = '{"content":"foo", "filename":"bar.txt"}'
    resp = resp.form.submit('submit')
    assert resp.location
    sendmail = Workflow.get(workflow.id).get_status(st1.id).items[0]
    assert sendmail.attachments == ['form_var_upload_raw', 'form_var_upload2_raw', 'form_fbo3_3x',
                                    '{"content":"foo", "filename":"bar.txt"}']

    # remove some backoffice fields: varnameless fbo3 disapear
    workflow = Workflow.get(workflow.id)
    workflow.backoffice_fields_formdef.fields = [
        fields.FileField(id='bo2', label='bo field 2', type='file', varname='upload2'),
    ]
    workflow.store()
    resp = app.get(item_url)
    resp = resp.form.submit('submit')
    assert resp.location
    sendmail = Workflow.get(workflow.id).get_status(st1.id).items[0]
    assert sendmail.attachments == ['form_var_upload_raw', 'form_var_upload2_raw',
                                    '{"content":"foo", "filename":"bar.txt"}']
    # remove all backoffice fields
    workflow = Workflow.get(workflow.id)
    workflow.backoffice_fields_formdef.fields = []
    workflow.store()
    resp = app.get(item_url)
    assert "Attachments (Python expressions)" in resp.text
    resp = resp.form.submit('submit')
    assert resp.location
    sendmail = Workflow.get(workflow.id).get_status(st1.id).items[0]
    assert sendmail.attachments == ['form_var_upload_raw', 'form_var_upload2_raw',
                                    '{"content":"foo", "filename":"bar.txt"}']

    # check condition has been saved as None, not {}.
    assert sendmail.condition is None

    resp = app.get(item_url)
    resp.form['condition$type'] = 'python'
    resp.form['condition$value_python'] = 'True'
    resp = resp.form.submit('submit')
    sendmail = Workflow.get(workflow.id).get_status(st1.id).items[0]
    assert sendmail.condition == {'type': 'python', 'value': 'True'}

def test_workflows_edit_jump_previous(pub):
    create_superuser(pub)
    role = create_role()
    Workflow.wipe()
    workflow = Workflow(name='foo')
    st1 = workflow.add_status(name='baz')

    jump = JumpWorkflowStatusItem()
    jump.id = '_jump'
    jump.timeout = 86400
    st1.items.append(jump)
    jump.parent = st1

    ac1 = workflow.add_global_action('Action', 'ac1')

    jump_global = JumpWorkflowStatusItem()
    jump_global.id = '_jump'
    jump_global.timeout = 86400
    ac1.items.append(jump_global)
    jump_global.parent = ac1

    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/status/1/items/_jump/')
    assert not 'Previously Marked Status' in [x[2] for x in resp.form['status'].options]

    jump.set_marker_on_status = True
    workflow.store()
    resp = app.get('/backoffice/workflows/1/status/1/items/_jump/')
    assert 'Previously Marked Status' in [x[2] for x in resp.form['status'].options]

    jump.set_marker_on_status = False
    workflow.store()
    resp = app.get('/backoffice/workflows/1/status/1/items/_jump/')
    assert not 'Previously Marked Status' in [x[2] for x in resp.form['status'].options]

    jump_global.set_marker_on_status = True
    workflow.store()
    resp = app.get('/backoffice/workflows/1/status/1/items/_jump/')
    assert 'Previously Marked Status' in [x[2] for x in resp.form['status'].options]

    resp = app.get('/backoffice/workflows/1/global-actions/ac1/items/_jump/')
    assert 'Previously Marked Status' in [x[2] for x in resp.form['status'].options]

    jump_global.set_marker_on_status = False
    workflow.store()
    resp = app.get('/backoffice/workflows/1/global-actions/ac1/items/_jump/')
    assert not 'Previously Marked Status' in [x[2] for x in resp.form['status'].options]

def test_workflows_edit_jump_timeout(pub):
    create_superuser(pub)
    role = create_role()
    Workflow.wipe()
    workflow = Workflow(name='foo')
    st1 = workflow.add_status(name='baz')

    jump = JumpWorkflowStatusItem()
    jump.id = '_jump'
    jump.status = '1'
    jump.timeout = 86400
    st1.items.append(jump)
    jump.parent = st1

    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/status/1/items/_jump/')
    resp.form['timeout'] = ''
    resp = resp.form.submit('submit').follow().follow()
    assert Workflow.get(workflow.id).possible_status[0].items[0].timeout is None
    assert 'Automatic Jump (baz)' in resp.text

    resp = app.get('/backoffice/workflows/1/status/1/items/_jump/')
    resp.form['timeout'] = '90 minutes'
    resp = resp.form.submit('submit').follow().follow()
    assert Workflow.get(workflow.id).possible_status[0].items[0].timeout == 5400
    assert 'Automatic Jump (to baz, timeout)' in resp.text

    resp = app.get('/backoffice/workflows/1/status/1/items/_jump/')
    resp.form['timeout'] = '=5400'
    resp = resp.form.submit('submit').follow().follow()
    assert Workflow.get(workflow.id).possible_status[0].items[0].timeout == '=5400'
    assert 'Automatic Jump (to baz, timeout)' in resp.text

    # check field switched to being a computed expression widget
    resp = app.get('/backoffice/workflows/1/status/1/items/_jump/')
    assert resp.text.count('ComputedExpressionWidget') == 1

def test_workflows_edit_sms_action(pub):
    create_superuser(pub)
    role = create_role()
    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.add_status(name='baz')
    workflow.store()

    pub.cfg['sms'] = {'mode': 'foobar'}
    pub.write_cfg()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('baz')

    resp.form['action-interaction'] = 'SMS'
    resp = resp.form.submit().follow()

    resp = resp.click('SMS')
    resp = resp.form.submit('to$add_element')
    resp = resp.form.submit('to$add_element')
    resp = resp.form.submit('to$add_element')
    resp.form['to$element1$value_text'] = '12345'
    resp = resp.form.submit('submit')
    assert Workflow.get(workflow.id).possible_status[0].items[0].to == ['12345']

def test_workflows_edit_display_form_action(pub):
    create_superuser(pub)
    role = create_role()
    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.add_status(name='baz')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('baz')

    resp.forms[0]['action-interaction'] = 'Form'
    resp = resp.forms[0].submit()
    resp = resp.follow()

    resp = resp.click(re.compile('^Form$'))
    resp = resp.click('Edit Fields')

    resp.form['label'] = 'foobar'
    resp.form['type'] = 'Text (line)'
    resp = resp.form.submit()

    resp = resp.follow()
    assert 'foobar' in resp.text
    resp = resp.click('Edit')
    assert not 'display_locations' in resp.form.fields.keys()
    assert 'condition$type' in resp.form.fields.keys()
    resp = resp.form.submit('cancel')
    resp = resp.follow()
    resp = resp.click('Remove')
    assert 'You are about to remove the "foobar" field.' in resp.text
    assert not 'Warning:' in resp.text

def test_workflows_edit_choice_action(pub):
    create_superuser(pub)
    role = create_role()
    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.add_status(name='baz')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('baz')

    resp.forms[0]['action-status-change'] = 'Manual Jump'
    resp = resp.forms[0].submit()
    resp = resp.follow()

    resp = resp.click(href='items/1/', index=0)
    assert 'Previously Marked Status' not in [x[2] for x in resp.form['status'].options]
    resp.form['status'].value = 'baz'
    resp = resp.form.submit('submit')
    resp = resp.follow()
    resp = resp.follow()

    resp = resp.click(href='items/1/', index=0)
    resp.form['set_marker_on_status'].value = True
    resp = resp.form.submit('submit')
    resp = resp.follow()
    resp = resp.follow()

    resp = resp.click(href='items/1/', index=0)
    assert 'Previously Marked Status' in [x[2] for x in resp.form['status'].options]

def test_workflows_edit_choice_action_functions_only(pub):
    create_superuser(pub)
    role = create_role()
    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.add_status(name='baz')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('baz')

    resp.forms[0]['action-status-change'] = 'Manual Jump'
    resp = resp.forms[0].submit()
    resp = resp.follow()

    resp = resp.click(href='items/1/', index=0)
    assert 'foobar' in [x[2] for x in resp.form['by$element0'].options]
    assert '_receiver' in [x[0] for x in resp.form['by$element0'].options]

    pub.site_options.set('options', 'workflow-functions-only', 'true')
    with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
        pub.site_options.write(fd)

    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('baz')
    resp = resp.click(href='items/1/', index=0)
    assert 'foobar' not in [x[2] for x in resp.form['by$element0'].options]
    assert '_receiver' in [x[0] for x in resp.form['by$element0'].options]

def test_workflows_action_subpath(pub):
    create_superuser(pub)
    role = create_role()
    Workflow.wipe()
    workflow = Workflow(name='foo')
    baz_status = workflow.add_status(name='baz')
    display_message = DisplayMessageWorkflowStatusItem()
    display_message.parent = baz_status
    baz_status.items.append(display_message)
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (
        workflow.id, baz_status.id))

    resp = app.get('/backoffice/workflows/%s/status/%s/items/1/crash' % (
        workflow.id, baz_status.id), status=404)

def test_workflows_display_action_ezt_validation(pub):
    create_superuser(pub)
    role = create_role()
    Workflow.wipe()
    workflow = Workflow(name='foo')
    baz_status = workflow.add_status(name='baz')
    display_message = DisplayMessageWorkflowStatusItem()
    display_message.parent = baz_status
    baz_status.items.append(display_message)
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (
        workflow.id, baz_status.id))
    resp.form['message'] = 'Hello world'
    resp = resp.form.submit('submit')
    assert Workflow.get(workflow.id).possible_status[0].items[0].message == 'Hello world'

    resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (
        workflow.id, baz_status.id))
    resp.form['message'] = '{% if test %}test{% endif %}'  # valid Django
    resp = resp.form.submit('submit')
    assert Workflow.get(workflow.id).possible_status[0].items[0].message == '{% if test %}test{% endif %}'

    resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (
        workflow.id, baz_status.id))
    resp.form['message'] = '{% if test %}test{% end %}'  # invalid Django
    resp = resp.form.submit('submit')
    assert 'syntax error in Django template' in resp.text

    resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (
        workflow.id, baz_status.id))
    resp.form['message'] = '[if-any test]test[end]'  # valid ezt
    resp = resp.form.submit('submit')
    assert Workflow.get(workflow.id).possible_status[0].items[0].message == '[if-any test]test[end]'

    resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (
        workflow.id, baz_status.id))
    resp.form['message'] = '[is test][end]'  # invalid ezt
    resp = resp.form.submit('submit')
    assert 'syntax error in ezt template' in resp.text

def test_workflows_delete_action(pub):
    create_superuser(pub)
    role = create_role()
    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.add_status(name='baz')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('baz')

    resp.forms[0]['action-interaction'] = 'Email'
    resp = resp.forms[0].submit()
    resp = resp.follow()
    assert 'Email' in resp.text

    resp = resp.click(href='items/1/delete')
    resp = resp.form.submit('cancel')
    assert resp.location == 'http://example.net/backoffice/workflows/1/status/1/'
    resp = resp.follow()
    resp = resp.click(href='items/1/delete')
    resp = resp.form.submit('submit')
    assert resp.location == 'http://example.net/backoffice/workflows/1/status/1/'
    resp = resp.follow()
    assert Workflow.get(workflow.id).possible_status[0].items == []

def test_workflows_variables(pub):
    create_superuser(pub)
    create_role()

    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click(href='variables/')
    assert resp.location == 'http://example.net/backoffice/workflows/1/variables/fields/'
    resp = resp.follow()

    # makes sure we can't add page fields
    assert 'value="Page"' not in resp.text

    # add a simple field
    resp.forms[0]['label'] = 'foobar'
    resp.forms[0]['type'] = 'Text (line)'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/workflows/1/variables/fields/'
    resp = resp.follow()

    # check it's been saved correctly
    assert 'foobar' in resp.text
    assert len(Workflow.get(1).variables_formdef.fields) == 1
    assert Workflow.get(1).variables_formdef.fields[0].key == 'string'
    assert Workflow.get(1).variables_formdef.fields[0].label == 'foobar'

def test_workflows_variables_edit(pub):
    test_workflows_variables(pub)

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click(href='variables/', index=0)
    assert resp.location == 'http://example.net/backoffice/workflows/1/variables/fields/'
    resp = resp.follow()
    resp = resp.click('Edit', href='1/')
    assert resp.forms[0]['varname$name'].value == 'foobar'
    assert not 'varname$select' in resp.forms[0].fields

    workflow = Workflow.get(1)
    baz_status = workflow.add_status(name='baz')
    display_message = DisplayMessageWorkflowStatusItem()
    display_message.parent = baz_status
    baz_status.items.append(display_message)
    workflow.store()

    resp = app.get('/backoffice/workflows/1/variables/fields/')
    resp = resp.click('Edit', href='1/')
    assert 'varname$select' in resp.forms[0].fields
    resp.forms[0]['varname$select'].value = '1*1*message'
    resp = resp.forms[0].submit('submit')

    assert Workflow.get(1).variables_formdef.fields[0].key == 'string'
    assert Workflow.get(1).variables_formdef.fields[0].varname == '1*1*message'

def test_workflows_variables_with_export_to_model_action(pub):
    test_workflows_variables(pub)

    workflow = Workflow.get(1)
    baz_status = workflow.add_status(name='baz')
    export_to = ExportToModel()
    export_to.label = 'create doc'
    baz_status.items.append(export_to)
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/variables/fields/')
    resp = resp.click('Edit', href='1/')

def test_workflows_backoffice_fields(pub):
    create_superuser(pub)
    create_role()

    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.add_status(name='baz')
    workflow.store()

    formdef = FormDef()
    formdef.name = 'form title'
    formdef.workflow_id = workflow.id
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('baz')
    assert not 'Set Backoffice Field' in resp.text

    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click(href='backoffice-fields/')
    assert resp.location == 'http://example.net/backoffice/workflows/1/backoffice-fields/fields/'
    resp = resp.follow()

    # makes sure we can't add page fields
    assert 'value="Page"' not in resp.text

    # add a simple field
    resp.forms[0]['label'] = 'foobar'
    resp.forms[0]['type'] = 'Text (line)'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/workflows/1/backoffice-fields/fields/'
    resp = resp.follow()
    assert Workflow.get(workflow.id).get_backoffice_fields()[0].required is True

    # check it's been saved correctly
    assert 'foobar' in resp.text
    assert len(Workflow.get(1).backoffice_fields_formdef.fields) == 1
    assert Workflow.get(1).backoffice_fields_formdef.fields[0].id.startswith('bo')
    assert Workflow.get(1).backoffice_fields_formdef.fields[0].key == 'string'
    assert Workflow.get(1).backoffice_fields_formdef.fields[0].label == 'foobar'

    backoffice_field_id = Workflow.get(1).backoffice_fields_formdef.fields[0].id
    formdef = FormDef.get(formdef.id)
    data_class = formdef.data_class()
    data_class.wipe()
    formdata = data_class()
    formdata.data = {backoffice_field_id: 'HELLO'}
    formdata.status = 'wf-new'
    formdata.store()

    assert data_class.get(formdata.id).data[backoffice_field_id] == 'HELLO'

    # check the "set backoffice fields" action is now available
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('baz')
    resp.forms[0]['action-formdata-action'] = 'Backoffice Data'
    resp = resp.forms[0].submit()
    resp = resp.follow()

    # add a second field
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click(href='backoffice-fields/', index=0)
    assert resp.location == 'http://example.net/backoffice/workflows/1/backoffice-fields/fields/'
    resp = resp.follow()
    resp.forms[0]['label'] = 'foobar2'
    resp.forms[0]['type'] = 'Text (line)'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/workflows/1/backoffice-fields/fields/'
    resp = resp.follow()
    workflow = Workflow.get(workflow.id)
    assert len(workflow.backoffice_fields_formdef.fields) == 2
    first_field_id = workflow.backoffice_fields_formdef.fields[0].id
    assert workflow.backoffice_fields_formdef.fields[1].id != first_field_id

    # check there's no prefill field
    resp = app.get('/backoffice/workflows/1/backoffice-fields/fields/%s/' % workflow.backoffice_fields_formdef.fields[1].id)
    assert not 'prefill$type' in resp.form.fields.keys()

    # add a title field
    resp = app.get('/backoffice/workflows/1/backoffice-fields/fields/')
    resp.forms[0]['label'] = 'foobar3'
    resp.forms[0]['type'] = 'Title'
    resp = resp.form.submit()
    workflow = Workflow.get(workflow.id)
    assert len(workflow.backoffice_fields_formdef.fields) == 3

    # check backoffice fields are available in set backoffice fields action
    resp = app.get('/backoffice/workflows/1/')
    resp = resp.click('baz') # status
    resp = resp.click('Backoffice Data')
    options = [x[2] for x in resp.form['fields$element0$field_id'].options]
    assert '' in options
    assert 'foobar' in options
    assert 'foobar2' in options
    assert 'foobar3' not in options

    resp.form['fields$element0$field_id'] = first_field_id
    resp.form['fields$element0$value$value_text'] = 'Hello'
    resp = resp.form.submit('submit')
    workflow = Workflow.get(workflow.id)
    assert workflow.possible_status[0].items[0].fields == [{'field_id': first_field_id, 'value': 'Hello'}]

def test_workflows_functions(pub):
    create_superuser(pub)
    create_role()

    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('add function')
    resp = resp.forms[0].submit('cancel')
    assert set(Workflow.get(workflow.id).roles.keys()) == set(['_receiver'])

    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('add function')
    resp.forms[0]['name'] = 'Other Function'
    resp = resp.forms[0].submit('submit')
    assert set(Workflow.get(workflow.id).roles.keys()) == set(['_receiver', '_other-function'])
    assert Workflow.get(workflow.id).roles['_other-function'] == 'Other Function'

    # test rename
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('Other Function')
    resp = resp.forms[0].submit('cancel')

    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('Other Function')
    resp.forms[0]['name'] = 'Other Renamed Function'
    resp = resp.forms[0].submit('submit')
    assert set(Workflow.get(workflow.id).roles.keys()) == set(['_receiver', '_other-function'])
    assert Workflow.get(workflow.id).roles['_other-function'] == 'Other Renamed Function'

    # test new function with older name
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('add function')
    resp.forms[0]['name'] = 'Other Function'
    resp = resp.forms[0].submit('submit')
    assert set(Workflow.get(workflow.id).roles.keys()) == set(
            ['_receiver', '_other-function', '_other-function-2'])
    assert Workflow.get(workflow.id).roles['_other-function'] == 'Other Renamed Function'
    assert Workflow.get(workflow.id).roles['_other-function-2'] == 'Other Function'

    # test removal
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('Other Renamed Function')
    resp = resp.forms[0].submit('delete')
    assert set(Workflow.get(workflow.id).roles.keys()) == set(['_receiver', '_other-function-2'])

    # make sure it's not possible to remove the "_receiver" key
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('Recipient')
    assert not 'delete' in resp.forms[0].fields

def test_workflows_functions_vs_visibility(pub):
    create_superuser(pub)
    create_role()

    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.possible_status = Workflow.get_default_workflow().possible_status[:]
    workflow.store()

    # restrict visibility
    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('Just Submitted')
    resp = resp.click('Change Display Settings')
    resp.forms[0]['hide_status_from_user'].checked = True
    resp = resp.forms[0].submit()
    assert Workflow.get(workflow.id).possible_status[0].visibility == ['_receiver']

    # add function, make sure visibility follows
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('add function')
    resp.forms[0]['name'] = 'Other Function'
    resp = resp.forms[0].submit('submit')
    assert set(Workflow.get(workflow.id).roles.keys()) == set(['_receiver', '_other-function'])
    assert Workflow.get(workflow.id).roles['_other-function'] == 'Other Function'
    assert set(Workflow.get(workflow.id).possible_status[0].visibility) == set(
            ['_receiver', '_other-function'])

    # restrict visibility in a different status, check it gets all the
    # functions
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('Rejected')
    resp = resp.click('Change Display Settings')
    resp.forms[0]['hide_status_from_user'].checked = True
    resp = resp.forms[0].submit()
    assert set(Workflow.get(workflow.id).possible_status[2].visibility) == set(
            ['_receiver', '_other-function'])

def test_workflows_global_actions(pub):
    create_superuser(pub)
    create_role()

    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('add global action')
    resp = resp.forms[0].submit('cancel')
    assert not Workflow.get(workflow.id).global_actions

    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('add global action')
    resp.forms[0]['name'] = 'Global Action'
    resp = resp.forms[0].submit('submit')

    # test adding action with same name
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('add global action')
    resp.forms[0]['name'] = 'Global Action'
    resp = resp.forms[0].submit('submit')
    assert 'There is already an action with that name.' in resp.text

    # test rename
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('Global Action')
    resp = resp.click('Change Action Name')
    resp = resp.form.submit('cancel')
    resp = resp.follow()
    resp = resp.click('Change Action Name')
    resp.forms[0]['name'] = 'Renamed Action'
    resp = resp.forms[0].submit('submit')
    assert Workflow.get(workflow.id).global_actions[0].name == 'Renamed Action'

    # test removal
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('Renamed Action')
    resp = resp.click('Delete')
    resp = resp.form.submit('cancel')
    resp = resp.follow()
    resp = resp.click('Delete')
    resp = resp.form.submit('delete')
    assert not Workflow.get(workflow.id).global_actions

def test_workflows_global_actions_edit(pub):
    create_superuser(pub)
    create_role()

    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('add global action')
    resp.forms[0]['name'] = 'Global Action'
    resp = resp.forms[0].submit('submit')
    resp = resp.follow()

    # test adding all actions
    for category in ('status-change', 'interaction', 'formdata-action', 'user-action'):
        for action in [x[0] for x in resp.forms[0]['action-%s' % category].options if x[0]]:
            resp.forms[0]['action-%s' % category] = action
            resp = resp.forms[0].submit()
            resp = resp.follow()

    # test visiting
    action_id = Workflow.get(workflow.id).global_actions[0].id
    for item in Workflow.get(workflow.id).global_actions[0].items:
        resp = app.get('/backoffice/workflows/%s/global-actions/%s/items/%s/' % (
            workflow.id, action_id, item.id))

    # test modifying a trigger
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('Global Action')
    assert len(Workflow.get(workflow.id).global_actions[0].triggers) == 1
    resp = resp.click(href='triggers/%s/' % Workflow.get(workflow.id).global_actions[0].triggers[0].id,
            index=0)
    assert resp.form['roles$element0'].value == 'None'
    resp.form['roles$element0'].value = '_receiver'
    resp = resp.form.submit('submit')
    assert Workflow.get(workflow.id).global_actions[0].triggers[0].roles == ['_receiver']

    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('Global Action')
    resp = resp.click(href='triggers/%s/' % Workflow.get(workflow.id).global_actions[0].triggers[0].id,
            index=0)
    assert resp.form['roles$element0'].value == '_receiver'
    resp.form['roles$element1'].value = '_submitter'
    resp = resp.form.submit('submit')
    assert Workflow.get(workflow.id).global_actions[0].triggers[0].roles == [
            '_receiver', '_submitter']

    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('Global Action')
    resp = resp.click(href='triggers/%s/' % Workflow.get(workflow.id).global_actions[0].triggers[0].id,
            index=0)
    resp.form['roles$element1'].value = 'None'
    resp = resp.form.submit('submit')
    assert Workflow.get(workflow.id).global_actions[0].triggers[0].roles == ['_receiver']

def test_workflows_global_actions_timeout_triggers(pub):
    create_superuser(pub)
    create_role()

    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('add global action')
    resp.forms[0]['name'] = 'Global Action'
    resp = resp.forms[0].submit('submit')
    resp = resp.follow()

    # test removing the existing manual trigger
    resp = resp.click(href='triggers/%s/delete' % Workflow.get(workflow.id).global_actions[0].triggers[0].id)
    resp = resp.forms[0].submit()
    resp = resp.follow()

    assert len(Workflow.get(workflow.id).global_actions[0].triggers) == 0

    # test adding a timeout trigger
    resp.forms[1]['type'] = 'Automatic'
    resp = resp.forms[1].submit()
    resp = resp.follow()

    assert 'Automatic (not configured)' in resp.text

    resp = resp.click(href='triggers/%s/' % Workflow.get(workflow.id).global_actions[0].triggers[0].id, index=0)
    for invalid_value in ('foobar', '-'):
        resp.form['timeout'] = invalid_value
        resp = resp.form.submit('submit')
        assert 'wrong format' in resp.text
    resp.form['timeout'] = ''
    resp = resp.form.submit('submit')
    assert 'required field' in resp.text
    resp.form['timeout'] = '3'
    resp = resp.form.submit('submit').follow()

    assert Workflow.get(workflow.id).global_actions[0].triggers[0].timeout == '3'

    resp = resp.click(href='triggers/%s/' % Workflow.get(workflow.id).global_actions[0].triggers[0].id, index=0)
    resp.form['timeout'] = '-2'
    resp = resp.form.submit('submit').follow()
    assert Workflow.get(workflow.id).global_actions[0].triggers[0].timeout == '-2'

def test_workflows_criticality_levels(pub):
    create_superuser(pub)
    create_role()

    Workflow.wipe()
    workflow = Workflow(name='foo')
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('add criticality level')
    resp = resp.forms[0].submit('cancel')
    assert not Workflow.get(workflow.id).criticality_levels

    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('add criticality level')
    resp.forms[0]['name'] = 'vigilance'
    resp = resp.forms[0].submit('submit')
    assert len(Workflow.get(workflow.id).criticality_levels) == 1
    assert Workflow.get(workflow.id).criticality_levels[0].name == 'vigilance'

    # test rename
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('vigilance')
    resp = resp.forms[0].submit('cancel')

    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('vigilance')
    resp.forms[0]['name'] = 'Vigilance'
    resp = resp.forms[0].submit('submit')
    assert len(Workflow.get(workflow.id).criticality_levels) == 1
    assert Workflow.get(workflow.id).criticality_levels[0].name == 'Vigilance'

    # add a second level
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('add criticality level')
    resp.forms[0]['name'] = 'Alerte attentat'
    resp = resp.forms[0].submit('submit')
    assert len(Workflow.get(workflow.id).criticality_levels) == 2
    assert Workflow.get(workflow.id).criticality_levels[0].name == 'Vigilance'
    assert Workflow.get(workflow.id).criticality_levels[1].name == 'Alerte attentat'

    # test reorder
    level1_id = Workflow.get(workflow.id).criticality_levels[0].id
    level2_id = Workflow.get(workflow.id).criticality_levels[1].id
    app.get('/backoffice/workflows/%s/update_criticality_levels_order?order=%s;%s;' % (
            workflow.id, level1_id, level2_id))
    assert Workflow.get(workflow.id).criticality_levels[0].id == level1_id
    assert Workflow.get(workflow.id).criticality_levels[1].id == level2_id
    app.get('/backoffice/workflows/%s/update_criticality_levels_order?order=%s;%s;' % (
            workflow.id, level2_id, level1_id))
    assert Workflow.get(workflow.id).criticality_levels[0].id == level2_id
    assert Workflow.get(workflow.id).criticality_levels[1].id == level1_id

    # test removal
    resp = app.get('/backoffice/workflows/%s/' % workflow.id)
    resp = resp.click('Vigilance')
    resp = resp.forms[0].submit('delete-level')
    assert len(Workflow.get(workflow.id).criticality_levels) == 1

def test_workflows_wscall_label(pub):
    create_superuser(pub)
    create_role()

    Workflow.wipe()
    workflow = Workflow(name='foo')
    baz_status = workflow.add_status(name='baz')
    wscall = WebserviceCallStatusItem()
    wscall.parent = baz_status
    baz_status.items.append(wscall)
    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/%s/status/%s/' % (workflow.id, baz_status.id))
    assert 'Webservice' in resp.text
    assert 'Webservice (' not in resp.text

    wscall.label = 'foowscallbar'
    workflow.store()
    resp = app.get('/backoffice/workflows/%s/status/%s/' % (workflow.id, baz_status.id))
    assert 'Webservice (foowscallbar)' in resp.text

def test_workflows_inspect_view(pub):
    from wcs.workflows import WorkflowVariablesFieldsFormDef
    from wcs.wf.form import FormWorkflowStatusItem, WorkflowFormFieldsFormDef

    create_superuser(pub)
    role = create_role()

    Workflow.wipe()
    workflow = Workflow(name='foo')

    workflow.criticality_levels = [WorkflowCriticalityLevel(name='green')]

    workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
    workflow.backoffice_fields_formdef.fields = [
        fields.StringField(id='bo1', label='1st backoffice field',
            type='string', varname='backoffice_blah', required=True),
    ]

    workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow)
    workflow.variables_formdef.fields.append(fields.StringField(label='Test', type='string'))

    foo_status = workflow.add_status(name='foo')

    baz_status = workflow.add_status(name='baz')
    baz_status.backoffice_info_text = 'Info text'
    wscall = WebserviceCallStatusItem()
    wscall.parent = baz_status
    baz_status.items.append(wscall)

    baz_status.backoffice_info_text = '<p>Hello</p>'

    display_form = FormWorkflowStatusItem()
    display_form.id = '_x'
    display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
    display_form.formdef.fields.append(fields.StringField(label='Test', type='string'))
    display_form.formdef.fields.append(fields.StringField(label='Test2', type='string'))
    display_form.backoffice_info_text = '<p>Foo</p>'
    baz_status.items.append(display_form)
    display_form.parent = baz_status

    jump = JumpWorkflowStatusItem()
    jump.id = '_jump'
    jump.timeout = 86400
    jump.status = foo_status.id
    baz_status.items.append(jump)
    jump.parent = baz_status

    invalid_jump = JumpWorkflowStatusItem()
    invalid_jump.id = '_invalid_jump'
    invalid_jump.status = 'xxx'
    baz_status.items.append(invalid_jump)
    invalid_jump.parent = baz_status

    ac1 = workflow.add_global_action('Action', 'ac1')
    ac1.backoffice_info_text = '<p>Foo</p>'

    add_to_journal = RegisterCommenterWorkflowStatusItem()
    add_to_journal.id = '_add_to_journal'
    add_to_journal.comment = 'HELLO WORLD'
    ac1.items.append(add_to_journal)
    add_to_journal.parent = ac1

    trigger = ac1.triggers[0]
    assert trigger.key == 'manual'
    trigger.roles = [role.id]

    workflow.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/%s/inspect' % workflow.id)

def test_workflows_unused(pub):
    create_superuser(pub)
    FormDef.wipe()
    Workflow.wipe()
    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/')
    assert not 'Unused workflows' in resp.text

    workflow = Workflow(name='Workflow One')
    workflow.store()
    resp = app.get('/backoffice/workflows/')
    assert 'Unused workflows' in resp.text

    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()
    resp = app.get('/backoffice/workflows/')
    assert 'Unused workflows' in resp.text

    formdef.workflow = workflow
    formdef.store()
    resp = app.get('/backoffice/workflows/')
    assert not 'Unused workflows' in resp.text

    workflow = Workflow(name='Workflow Two')
    workflow.store()
    resp = app.get('/backoffice/workflows/')
    assert 'Unused workflows' in resp.text

def test_users_roles_menu_entries(pub):
    create_superuser(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/')
    assert 'Users' in resp.text
    assert 'Roles' in resp.text
    resp = app.get('/backoffice/menu.json')
    assert 'Users' in [x['label'] for x in resp.json]
    assert 'Roles' in [x['label'] for x in resp.json]

    # don't include users/roles in menu if roles are managed by an external
    # identity provider.
    pub.cfg['sp'] = {'idp-manage-roles': True}
    pub.write_cfg()

    resp = app.get('/backoffice/')
    assert 'Users' not in resp.text
    assert 'Roles' not in resp.text
    resp = app.get('/backoffice/menu.json')
    assert 'Users' not in [x['label'] for x in resp.json]
    assert 'Roles' not in [x['label'] for x in resp.json]

def test_users(pub):
    create_superuser(pub)
    app = login(get_app(pub))
    app.get('/backoffice/users/')

def test_users_new(pub):
    pub.user_class.wipe()
    create_superuser(pub)
    user_count = pub.user_class.count()
    account_count = PasswordAccount.count()
    app = login(get_app(pub))
    resp = app.get('/backoffice/users/')
    resp = resp.click('New User')
    resp.forms[0]['name'] = 'a new user'
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/users/'
    resp = resp.follow()
    assert 'a new user' in resp.text
    resp = resp.click('a new user')
    assert 'User - a new user' in resp.text
    assert pub.user_class.count() == user_count + 1
    assert PasswordAccount.count() == account_count

def test_users_new_with_account(pub):
    pub.user_class.wipe()
    create_superuser(pub)
    user_count = pub.user_class.count()
    account_count = PasswordAccount.count()
    app = login(get_app(pub))
    resp = app.get('/backoffice/users/')
    resp = resp.click('New User')
    resp.forms[0]['name'] = 'a second user'
    resp.forms[0]['method_password$username'] = 'second-user'
    resp.forms[0]['method_password$password'] = 'foobar'
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/users/'
    resp = resp.follow()
    assert 'a second user' in resp.text
    resp = resp.click('a second user')
    assert 'User - a second user' in resp.text
    assert pub.user_class.count() == user_count + 1
    assert PasswordAccount.count() == account_count + 1

def test_users_edit(pub):
    pub.user_class.wipe()
    create_superuser(pub)
    user = pub.user_class(name='foo bar')
    user.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/users/%s/' % user.id)
    resp = resp.click(href='edit')
    resp.forms[0]['is_admin'].checked = True
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/users/%s/' % user.id
    resp = resp.follow()

def test_users_edit_new_account(pub):
    pub.user_class.wipe()
    PasswordAccount.wipe()
    create_superuser(pub)
    user = pub.user_class(name='foo bar')
    user.store()
    account_count = PasswordAccount.count()

    app = login(get_app(pub))
    resp = app.get('/backoffice/users/%s/' % user.id)
    resp = resp.click(href='edit')
    resp.forms[0]['is_admin'].checked = True
    resp.forms[0]['method_password$username'] = 'foo'
    resp.forms[0]['method_password$password'] = 'bar'
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/users/%s/' % user.id
    resp = resp.follow()

    assert PasswordAccount.count() == account_count + 1

def test_users_edit_edit_account(pub):
    pub.user_class.wipe()
    PasswordAccount.wipe()
    create_superuser(pub)
    user = pub.user_class(name='foo bar')
    user.store()
    account = PasswordAccount(id='test')
    account.user_id = user.id
    account.store()
    assert PasswordAccount.has_key('test')

    app = login(get_app(pub))
    resp = app.get('/backoffice/users/%s/' % user.id)
    resp = resp.click(href='edit')
    resp.forms[0]['is_admin'].checked = True
    resp.forms[0]['method_password$username'] = 'foo' # change username
    resp.forms[0]['method_password$password'] = 'bar'
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/users/%s/' % user.id
    resp = resp.follow()

    # makes sure the old account has been removed
    assert not PasswordAccount.has_key('test')
    assert PasswordAccount.has_key('foo')
    assert PasswordAccount.get('foo').user_id == user.id

def test_users_edit_with_managing_idp(pub):
    create_role()
    pub.user_class.wipe()
    pub.cfg['sp'] = {'idp-manage-user-attributes': True}
    pub.write_cfg()
    PasswordAccount.wipe()
    create_superuser(pub)
    user = pub.user_class(name='foo bar')
    user.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/users/%s/' % user.id)
    assert '>Manage Roles<' in resp.text
    resp = resp.click(href='edit')
    assert not 'email' in resp.form.fields
    assert 'roles$added_elements' in resp.form.fields

    pub.cfg['sp'] = {'idp-manage-roles': True}
    pub.write_cfg()
    resp = app.get('/backoffice/users/%s/' % user.id)
    assert '>Edit<' in resp.text
    resp = resp.click(href='edit')
    assert 'email' in resp.form.fields
    assert not 'roles$added_elements' in resp.form.fields

    pub.cfg['sp'] = {'idp-manage-roles': True, 'idp-manage-user-attributes': True}
    pub.write_cfg()
    resp = app.get('/backoffice/users/%s/' % user.id)
    assert not '/edit' in resp.text

def test_users_delete(pub):
    pub.user_class.wipe()
    PasswordAccount.wipe()
    create_superuser(pub)
    user = pub.user_class(name='foo bar')
    user.store()
    account = PasswordAccount(id='test')
    account.user_id = user.id
    account.store()

    user_count = pub.user_class.count()
    account_count = PasswordAccount.count()

    app = login(get_app(pub))
    resp = app.get('/backoffice/users/%s/' % user.id)

    resp = resp.click(href='delete')
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/users/'
    resp = resp.follow()

    assert pub.user_class.count() == user_count - 1
    assert PasswordAccount.count() == account_count - 1

def test_users_pagination(pub):
    pub.user_class.wipe()
    PasswordAccount.wipe()
    create_superuser(pub)
    for i in range(50):
        user = pub.user_class(name='foo bar %s' % (i+1))
        user.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/users/')
    assert 'foo bar 10' in resp.text
    assert 'foo bar 30' not in resp.text

    resp = resp.click('Next Page')
    assert 'foo bar 10' not in resp.text
    assert 'foo bar 30' in resp.text

    resp = resp.click('Previous Page')
    assert 'foo bar 10' in resp.text
    assert 'foo bar 30' not in resp.text

    resp = resp.click('Next Page')
    resp = resp.click('Next Page')
    assert 'foo bar 50' in resp.text

def test_users_filter(pub):
    pub.user_class.wipe()
    PasswordAccount.wipe()
    create_superuser(pub)
    role = create_role()
    for i in range(50):
        user = pub.user_class(name='foo bar %s' % (i+1))
        user.store()

    for i in range(5):
        user = pub.user_class(name='baz bar %s' % (i+1))
        user.roles = [role.id]
        user.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/users/')
    assert 'admin' in resp.text # superuser
    assert 'foo bar 10' in resp.text # simple user

    # uncheck 'None'; unfortunately this doesn't work with webtest 1.3
    #   resp.forms[0].fields['role'][-1].checked = False
    #   resp = resp.forms[0].submit()
    # therefore we fall back on using the URL
    resp = app.get('/backoffice/users/?offset=0&limit=100&q=&filter=true&role=admin')
    assert '>Number of filtered users: 1<' in resp.text
    assert 'user-is-admin' in resp.text # superuser
    assert 'foo bar 1' not in resp.text # simple user
    assert 'baz bar 1' not in resp.text # user with role

    resp = app.get('/backoffice/users/?offset=0&limit=100&q=&filter=true&role=1')
    assert '>Number of filtered users: 5<' in resp.text
    assert 'user-is-admin' not in resp.text # superuser
    assert 'foo bar 10' not in resp.text # simple user
    assert 'baz bar 1' in resp.text # user with role

def test_users_search(pub):
    pub.user_class.wipe()
    PasswordAccount.wipe()
    create_superuser(pub)
    for i in range(20):
        user = pub.user_class(name='foo %s' % (i+1))
        user.store()
    for i in range(10):
        user = pub.user_class(name='bar %s' % (i+1))
        user.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/users/')
    assert 'foo 10' in resp.text

    resp.forms[0]['q'] = 'bar'
    resp = resp.forms[0].submit()
    assert 'foo 10' not in resp.text
    assert 'bar 10' in resp.text
    assert 'Number of filtered users: 10' in resp.text

def test_users_new_with_custom_formdef(pub):
    pub.user_class.wipe()
    formdef = UserFieldsFormDef(pub)
    formdef.fields.append(fields.StringField(id='3', label='test', type='string'))
    formdef.fields.append(fields.CommentField(id='4', label='test', type='comment'))
    formdef.fields.append(fields.FileField(id='5', label='test', type='file', required=False))
    formdef.store()

    create_superuser(pub)
    user_count = pub.user_class.count()
    account_count = PasswordAccount.count()
    app = login(get_app(pub))
    resp = app.get('/backoffice/users/')
    resp = resp.click('New User')
    resp.form['name'] = 'a new user'
    resp.form['f3'] = 'TEST'
    resp = resp.form.submit('submit')
    assert resp.location == 'http://example.net/backoffice/users/'
    resp = resp.follow()
    assert 'a new user' in resp.text
    resp = resp.click('a new user')
    assert 'User - a new user' in resp.text
    assert 'TEST' in resp.text
    assert pub.user_class.count() == user_count + 1
    assert PasswordAccount.count() == account_count

def test_users_display_roles(pub):
    pub.user_class.wipe()

    user = create_superuser(pub)
    role = create_role()
    user.roles = [role.id, 'XXX']
    user.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/users/%s/' % user.id)
    assert role.name in resp.text
    assert 'Unknown role (XXX)' in resp.text

def test_roles(pub):
    app = login(get_app(pub))
    app.get('/backoffice/roles/')

def test_roles_new(pub):
    Role.wipe()
    app = login(get_app(pub))
    resp = app.get('/backoffice/roles/')
    resp = resp.click('New Role')
    resp.forms[0]['name'] = 'a new role'
    resp.forms[0]['details'] = 'bla bla bla'
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/roles/'
    resp = resp.follow()
    assert 'a new role' in resp.text
    resp = resp.click('a new role')
    assert '<h2>a new role' in resp.text

    assert Role.get(1).name == 'a new role'
    assert Role.get(1).details == 'bla bla bla'

def test_roles_edit(pub):
    Role.wipe()
    role = Role(name='foobar')
    role.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/roles/1/')
    assert 'Holders of this role are granted access to the backoffice' in resp.text

    resp = resp.click(href='edit')
    assert resp.forms[0]['name'].value == 'foobar'
    resp.forms[0]['name'] = 'baz'
    resp.forms[0]['details'] = 'bla bla bla'
    resp.forms[0]['emails_to_members'].checked = True
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/roles/1/'
    resp = resp.follow()
    assert '<h2>baz' in resp.text
    assert 'Holders of this role will receive all emails adressed to the role.' in resp.text

    assert Role.get(1).details == 'bla bla bla'
    assert Role.get(1).emails_to_members == True

def test_roles_matching_formdefs(pub):
    Role.wipe()
    role = Role(name='foo')
    role.store()

    FormDef.wipe()
    app = login(get_app(pub))
    resp = app.get('/backoffice/roles/1/')
    assert 'form bar' not in resp.text

    formdef = FormDef()
    formdef.name = 'form bar'
    formdef.roles = [role.id]
    formdef.fields = []
    formdef.store()

    resp = app.get('/backoffice/roles/1/')
    assert 'form bar' in resp.text
    assert 'form baz' not in resp.text

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form baz'
    formdef.fields = []
    formdef.workflow_roles = {'_receiver': role.id}
    formdef.store()

    resp = app.get('/backoffice/roles/1/')
    assert 'form baz' in resp.text
    assert 'form bar' not in resp.text

def test_roles_delete(pub):
    Role.wipe()
    role = Role(name='foobar')
    role.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/roles/1/')

    resp = resp.click(href='delete')
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/roles/'
    resp = resp.follow()
    assert Role.count() == 0

def test_categories(pub):
    create_superuser(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/categories/')

def test_categories_legacy_urls(pub):
    create_superuser(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/categories/')
    assert resp.location.endswith('/backoffice/forms/categories/')
    resp = app.get('/backoffice/categories/1')
    assert resp.location.endswith('/backoffice/forms/categories/1')
    resp = app.get('/backoffice/categories/1/')
    assert resp.location.endswith('/backoffice/forms/categories/1/')

def test_categories_new(pub):
    create_superuser(pub)
    Category.wipe()
    app = login(get_app(pub))

    # go to the page and cancel
    resp = app.get('/backoffice/forms/categories/')
    resp = resp.click('New Category')
    resp = resp.forms[0].submit('cancel')
    assert resp.location == 'http://example.net/backoffice/forms/categories/'

    # go to the page and add a category
    resp = app.get('/backoffice/forms/categories/')
    resp = resp.click('New Category')
    resp.forms[0]['name'] = 'a new category'
    resp.forms[0]['description'] = 'description of the category'
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/forms/categories/'
    resp = resp.follow()
    assert 'a new category' in resp.text
    resp = resp.click('a new category')
    assert '<h2>a new category' in resp.text

    assert Category.get(1).name == 'a new category'
    assert Category.get(1).description == 'description of the category'

def test_categories_edit(pub):
    create_superuser(pub)
    Category.wipe()
    category = Category(name='foobar')
    category.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/categories/1/')
    assert 'no form associated to this category' in resp.text

    resp = resp.click(href='edit')
    assert resp.forms[0]['name'].value == 'foobar'
    resp.forms[0]['description'] = 'category description'
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/forms/categories/'
    resp = resp.follow()
    resp = resp.click('foobar')
    assert '<h2>foobar' in resp.text

    assert Category.get(1).description == 'category description'

def test_categories_edit_duplicate_name(pub):
    Category.wipe()
    category = Category(name='foobar')
    category.store()
    category = Category(name='foobar2')
    category.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/categories/1/')

    resp = resp.click(href='edit')
    assert resp.forms[0]['name'].value == 'foobar'
    resp.forms[0]['name'] = 'foobar2'
    resp = resp.forms[0].submit('submit')
    assert 'This name is already used' in resp.text

    resp = resp.forms[0].submit('cancel')
    assert resp.location == 'http://example.net/backoffice/forms/categories/'

def test_categories_with_formdefs(pub):
    Category.wipe()
    category = Category(name='foobar')
    category.store()

    FormDef.wipe()
    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/categories/1/')
    assert 'form bar' not in resp.text

    formdef = FormDef()
    formdef.name = 'form bar'
    formdef.fields = []
    formdef.category_id = category.id
    formdef.store()

    resp = app.get('/backoffice/forms/categories/1/')
    assert 'form bar' in resp.text
    assert 'no form associated to this category' not in resp.text

def test_categories_delete(pub):
    Category.wipe()
    category = Category(name='foobar')
    category.store()

    FormDef.wipe()
    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/categories/1/')

    resp = resp.click(href='delete')
    resp = resp.forms[0].submit('cancel')
    assert resp.location == 'http://example.net/backoffice/forms/categories/1/'
    assert Category.count() == 1

    resp = app.get('/backoffice/forms/categories/1/')
    resp = resp.click(href='delete')
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/forms/categories/'
    resp = resp.follow()
    assert Category.count() == 0


def test_categories_edit_description(pub):
    Category.wipe()
    category = Category(name='foobar')
    category.description = 'category description'
    category.store()

    app = login(get_app(pub))
    # this URL is used for editing from the frontoffice, there's no link
    # pointing to it in the admin.
    resp = app.get('/backoffice/forms/categories/1/description')
    assert resp.forms[0]['description'].value == 'category description'
    resp.forms[0]['description'] = 'updated description'

    # check cancel doesn't save the change
    resp2 = resp.forms[0].submit('cancel')
    assert resp2.location == 'http://example.net/backoffice/forms/categories/1/'
    assert Category.get(1).description == 'category description'

    # check submit does it properly
    resp2 = resp.forms[0].submit('submit')
    assert resp2.location == 'http://example.net/backoffice/forms/categories/1/'
    resp2 = resp2.follow()
    assert Category.get(1).description == 'updated description'

def test_categories_new_duplicate_name(pub):
    Category.wipe()
    category = Category(name='foobar')
    category.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/categories/')
    resp = resp.click('New Category')
    resp.forms[0]['name'] = 'foobar'
    resp = resp.forms[0].submit('submit')
    assert 'This name is already used' in resp.text

def test_categories_reorder(pub):
    Category.wipe()
    category = Category(name='foo')
    category.store()
    category = Category(name='bar')
    category.store()
    category = Category(name='baz')
    category.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/forms/categories/update_order?order=1;2;3;')
    categories = Category.select()
    Category.sort_by_position(categories)
    assert [x.id for x in categories] == ['1', '2', '3']

    resp = app.get('/backoffice/forms/categories/update_order?order=3;1;2;')
    categories = Category.select()
    Category.sort_by_position(categories)
    assert [x.id for x in categories] == ['3', '1', '2']

def test_settings(pub):
    create_superuser(pub)
    app = login(get_app(pub))
    app.get('/backoffice/settings/')

    app.get('/backoffice/settings/misc')
    app.get('/backoffice/settings/debug_options')
    app.get('/backoffice/settings/language')
    app.get('/backoffice/settings/import')
    app.get('/backoffice/settings/export')
    app.get('/backoffice/settings/identification')
    app.get('/backoffice/settings/sitename')
    app.get('/backoffice/settings/sms')
    app.get('/backoffice/settings/session')
    app.get('/backoffice/settings/admin-permissions')

def test_settings_disabled_screens(pub):
    create_superuser(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/settings/')
    assert 'Identification' in resp.text
    assert 'Theme' in resp.text

    if not pub.site_options.has_section('options'):
        pub.site_options.add_section('options')
    pub.site_options.set('options', 'settings-disabled-screens', 'identification, theme')
    with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
        pub.site_options.write(fd)
    resp = app.get('/backoffice/settings/')
    assert 'Identification' not in resp.text
    assert 'Theme' not in resp.text

def test_settings_export_import(pub, studio):
    def wipe():
        FormDef.wipe()
        CardDef.wipe()
        Workflow.wipe()
        Role.wipe()
        Category.wipe()
        NamedDataSource.wipe()
        NamedWsCall.wipe()

    wipe()
    create_superuser(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/settings/export')
    resp = resp.form.submit('cancel')
    resp = app.get('/backoffice/settings/export')
    resp = resp.form.submit('submit')
    assert resp.location.startswith('http://example.net/backoffice/settings/export?job=')
    job_id = urlparse.parse_qs(urlparse.urlparse(resp.location).query)['job'][0]
    resp = resp.follow()
    assert 'completed' in resp.text
    resp = resp.click('Download Export')
    zip_content = BytesIO(resp.body)
    zipf = zipfile.ZipFile(zip_content, 'a')
    filelist = zipf.namelist()
    assert len(filelist) == 0

    # check afterjob ajax call
    status_resp = app.get('/afterjobs/' + job_id)
    assert status_resp.text == 'completed|completed'

    formdef = FormDef()
    formdef.name = 'foo'
    formdef.store()
    carddef = CardDef()
    carddef.name = 'bar'
    carddef.store()
    Category(name='baz').store()
    Role(name='qux').store()
    NamedDataSource(name='quux').store()
    NamedWsCall(name='corge').store()

    wf = Workflow(name='bar')
    st1 = wf.add_status('Status1', 'st1')
    export_to = ExportToModel()
    export_to.label = 'test'
    upload = QuixoteUpload('/foo/bar', content_type='application/vnd.oasis.opendocument.text')
    file_content = b'''PK\x03\x04\x14\x00\x00\x08\x00\x00\'l\x8eG^\xc62\x0c\'\x00'''
    upload.fp = BytesIO()
    upload.fp.write(file_content)
    upload.fp.seek(0)
    export_to.model_file = UploadedFile('models', 'export_to_model-1.upload', upload)
    st1.items.append(export_to)
    export_to.parent = st1
    wf.store()

    resp = app.get('/backoffice/settings/export')
    resp = resp.form.submit('submit')
    assert resp.location.startswith('http://example.net/backoffice/settings/export?job=')
    job_id = urlparse.parse_qs(urlparse.urlparse(resp.location).query)['job'][0]
    resp = resp.follow()
    resp = resp.click('Download Export')
    zip_content = BytesIO(resp.body)
    zipf = zipfile.ZipFile(zip_content, 'a')
    filelist = zipf.namelist()
    assert 'formdefs/1' not in filelist
    assert 'formdefs_xml/1' in filelist
    assert 'carddefs/1' not in filelist
    assert 'carddefs_xml/1' in filelist
    assert 'workflows/1' not in filelist
    assert 'workflows_xml/1' in filelist
    assert 'models/export_to_model-1.upload' not in filelist
    assert 'roles/1' in filelist
    assert 'categories/1' in filelist
    assert 'datasources/1' in filelist
    assert 'wscalls/corge' in filelist
    for filename in filelist:
        assert not '.indexes' in filename

    wipe()
    assert FormDef.count() == 0

    resp = app.get('/backoffice/settings/import')
    resp = resp.form.submit('cancel')

    resp = app.get('/backoffice/settings/import')
    resp.form['file'] = Upload('export.wcs', b'invalid content')
    resp = resp.form.submit('submit')
    assert 'Error: Not a valid export file' in resp.text

    resp = app.get('/backoffice/settings/import')
    resp.form['file'] = Upload('export.wcs', zip_content.getvalue())
    resp = resp.form.submit('submit')
    assert 'Imported successfully' in resp.text
    assert '1 forms' in resp.text
    assert '1 cards' in resp.text
    assert FormDef.count() == 1
    assert FormDef.select()[0].url_name == 'foo'
    assert CardDef.count() == 1
    assert CardDef.select()[0].url_name == 'bar'

    # check roles are found by name
    wipe()
    role = Role(name='qux')
    role.store()

    workflow = Workflow(name='Workflow One')
    st1 = workflow.add_status(name='st1')
    commentable = CommentableWorkflowStatusItem()
    commentable.id = '_commentable'
    commentable.by = [role.id]
    commentable.label = 'foobar'
    st1.items.append(commentable)
    commentable.parent = st1
    workflow.store()

    formdef = FormDef()
    formdef.name = 'foo'
    formdef.workflow_id = workflow.id
    formdef.roles = [role.id]
    formdef.backoffice_submission_roles = [role.id]
    formdef.workflow_roles = {'_receiver': role.id}
    formdef.store()

    resp = app.get('/backoffice/settings/export')
    resp.form['formdefs'] = True
    resp.form['workflows'] = True
    resp.form['roles'] = False
    resp.form['categories'] = False
    resp.form['datasources'] = False
    resp.form['wscalls'] = False
    resp = resp.form.submit('submit')
    assert resp.location.startswith('http://example.net/backoffice/settings/export?job=')
    job_id = urlparse.parse_qs(urlparse.urlparse(resp.location).query)['job'][0]
    resp = resp.follow()
    resp = resp.click('Download Export')
    zip_content = BytesIO(resp.body)
    zipf = zipfile.ZipFile(zip_content, 'a')
    filelist = zipf.namelist()
    assert 'formdefs_xml/%s' % formdef.id in filelist
    assert 'workflows_xml/%s' % workflow.id in filelist
    assert 'roles/%s' % role.id not in filelist

    FormDef.wipe()
    Workflow.wipe()
    Role.wipe()

    # create role beforehand, it should be matched by name
    role = Role(name='qux')
    role.id = '012345'
    role.store()

    resp = app.get('/backoffice/settings/import')
    resp.form['file'] = Upload('export.wcs', zip_content.getvalue())
    resp = resp.form.submit('submit')
    assert FormDef.select()[0].roles == ['012345']
    assert FormDef.select()[0].backoffice_submission_roles == ['012345']
    assert FormDef.select()[0].workflow_roles == {'_receiver': '012345'}
    assert Workflow.select()[0].possible_status[0].items[0].by == ['012345']

    # do not export roles when managed by idp
    pub.cfg['sp'] = {'idp-manage-roles': True}
    pub.write_cfg()
    resp = app.get('/backoffice/settings/export')
    resp = resp.form.submit('submit')
    assert resp.location.startswith('http://example.net/backoffice/settings/export?job=')
    job_id = urlparse.parse_qs(urlparse.urlparse(resp.location).query)['job'][0]
    resp = resp.follow()
    resp = resp.click('Download Export')
    zip_content = BytesIO(resp.body)
    zipf = zipfile.ZipFile(zip_content, 'a')
    filelist = zipf.namelist()
    assert len([x for x in filelist if 'roles/' in x]) == 0

def test_settings_themes(pub):
    create_superuser(pub)
    app = login(get_app(pub))

    # create mock theme
    os.mkdir(os.path.join(pub.app_dir, 'themes'))
    os.mkdir(os.path.join(pub.app_dir, 'themes', 'test'))
    fd = open(os.path.join(pub.app_dir, 'themes', 'test', 'desc.xml'), 'w')
    fd.write('<?xml version="1.0"?>'\
             '<theme name="test" version="1.0">'\
             '  <label>Test Theme</label>'\
             '</theme>')
    fd.close()

    resp = app.get('/backoffice/settings/themes')
    assert 'biglist themes' in resp.text
    assert 'Test Theme (1.0)' in resp.text

    # just for the kick, there's no support for uploading file in webtest 1.3
    resp = app.get('/backoffice/settings/themes')
    resp.click('Install New Theme')

    # select the theme
    resp = app.get('/backoffice/settings/themes')
    resp.forms[0]['theme'].value = 'test'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/settings/'

    resp = app.get('/backoffice/settings/themes')
    assert 'checked' in resp.text
    assert get_current_theme()['name'] == 'test'

def test_settings_template(pub):
    create_superuser(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/settings/template')

    # change template
    orig_value = resp.forms[0]['template'].value
    assert not 'foobar' in orig_value
    resp.forms[0]['template'] = orig_value + '<!-- foobar -->'
    resp = resp.forms[0].submit('submit')

    # restore default template
    resp = app.get('/backoffice/settings/template')
    assert 'foobar' in resp.forms[0]['template'].value
    resp = resp.forms[0].submit('restore-default')

    # check
    resp = app.get('/backoffice/settings/template')
    assert resp.forms[0]['template'].value == orig_value

def test_settings_user(pub):
    user = create_superuser(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/settings/users').follow().follow()

    # add a field
    resp.forms[1]['label'] = 'foobar'
    resp = resp.forms[1].submit()
    assert resp.location == 'http://example.net/backoffice/settings/users/fields/'
    resp = resp.follow()
    assert b'foobar' in pub.cfg['users']['formdef']
    assert 'foobar' in resp.text

    # set field as email
    resp.forms[0]['field_email'] = '1'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/settings/users/fields/'
    resp = resp.follow()
    assert pub.cfg['users']['field_email'] == '1'

    # and unset it
    resp.forms[0]['field_email'] = ''
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/settings/users/fields/'
    resp = resp.follow()
    assert pub.cfg['users']['field_email'] == None

    # add a comment field
    resp.forms[1]['label'] = 'barfoo'
    resp.forms[1]['type'] = 'Comment'
    resp = resp.forms[1].submit()
    assert resp.location == 'http://example.net/backoffice/settings/users/fields/'
    resp = resp.follow()
    assert b'barfoo' in pub.cfg['users']['formdef']
    assert 'barfoo' in resp.text

    # check fields are present in edit form
    resp = app.get('/backoffice/users/%s/edit' % user.id)
    assert 'barfoo' in resp.text
    assert 'f1' in resp.forms[0].fields
    assert 'email' in resp.forms[0].fields

    # check the email field is not displayed if it's overridden by a custom
    # field.
    pub.cfg['users']['field_email'] = '1'
    pub.write_cfg()
    resp = app.get('/backoffice/users/%s/edit' % user.id)
    assert 'f1' in resp.forms[0].fields
    assert 'email' not in resp.forms[0].fields

    # restore config
    pub.cfg['users']['field_email'] = None
    pub.write_cfg()

def test_settings_emails(pub):
    create_superuser(pub)
    app = login(get_app(pub))

    pub.cfg['debug'] = {'mail_redirection': 'foo@example.net'}
    pub.write_cfg()
    resp = app.get('/backoffice/settings/emails/')
    resp = resp.click('General Options')
    assert 'Warning: all emails are sent to &lt;foo@example.net&gt;' in resp.text

    pub.cfg['debug'] = {}
    pub.write_cfg()
    resp = app.get('/backoffice/settings/emails/')
    resp = resp.click('General Options')
    assert 'Warning: all emails are sent to &lt;foo@example.net&gt;' not in resp.text

    resp = app.get('/backoffice/settings/emails/')
    resp = resp.click('Approval of new account')
    resp.forms[0]['email-new-account-approved_subject'] = 'bla'
    resp.forms[0]['email-new-account-approved'] = 'bla bla bla'
    resp = resp.forms[0].submit()
    assert pub.cfg['emails']['email-new-account-approved_subject'] == 'bla'
    assert pub.cfg['emails']['email-new-account-approved'] == 'bla bla bla'

    # reset to default value
    resp = app.get('/backoffice/settings/emails/')
    resp = resp.click('Approval of new account')
    resp.forms[0]['email-new-account-approved_subject'] = 'Your account has been approved'
    resp = resp.forms[0].submit()
    assert pub.cfg['emails']['email-new-account-approved_subject'] is None

def test_settings_texts(pub):
    create_superuser(pub)
    app = login(get_app(pub))

    resp = app.get('/backoffice/settings/texts/')
    resp = resp.click('Text on top of the login page')
    resp.forms[0]['text-top-of-login'] = 'Hello world'
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/settings/texts/'
    assert pub.cfg['texts']['text-top-of-login'] == 'Hello world'

    resp = app.get('/backoffice/settings/texts/')
    resp = resp.click('Text on top of the login page')
    resp = resp.forms[0].submit('restore-default')
    assert resp.location == 'http://example.net/backoffice/settings/texts/'
    assert pub.cfg['texts']['text-top-of-login'] == None

@pytest.mark.skipif('lasso is None')
def test_settings_auth(pub):
    pub.user_class.wipe() # makes sure there are no users
    pub.cfg['identification'] = {}
    pub.write_cfg()
    app = get_app(pub)

    resp = app.get('/backoffice/settings/')
    assert not 'identification/password/' in resp.text
    assert not 'identification/idp/' in resp.text

    resp = resp.click('Identification')
    assert resp.forms[0]['methods$elementidp'].checked is False
    assert resp.forms[0]['methods$elementpassword'].checked is False
    resp.forms[0]['methods$elementidp'].checked = True
    resp = resp.forms[0].submit()
    resp = resp.follow()
    assert 'identification/idp/' in resp.text
    assert pub.cfg['identification']['methods'] == ['idp']

    resp = resp.click('Identification')
    assert resp.forms[0]['methods$elementidp'].checked is True
    assert resp.forms[0]['methods$elementpassword'].checked is False
    resp.forms[0]['methods$elementidp'].checked = False
    resp.forms[0]['methods$elementpassword'].checked = True
    resp = resp.forms[0].submit()
    resp = resp.follow()
    assert 'identification/password/' in resp.text
    assert pub.cfg['identification']['methods'] == ['password']

@pytest.mark.skipif('lasso is None')
def test_settings_idp(pub):
    # create admin session
    create_superuser(pub)
    app = login(get_app(pub))

    pub.cfg['identification'] = {'methods': ['idp']}
    pub.write_cfg()
    app.get('/saml/metadata', status=404)
    resp = app.get('/backoffice/settings/')
    resp = resp.click(href='identification/idp/')
    resp = resp.click('Service Provider')
    resp = resp.form.submit('generate_rsa').follow()
    resp = resp.form.submit('submit')
    resp = resp.follow()

    resp2 = resp.click('Identities')
    resp2 = resp2.form.submit('cancel').follow()
    resp2 = resp.click('Identities')
    resp2 = resp2.form.submit('submit')

    resp_metadata = app.get('/saml/metadata', status=200)
    assert resp_metadata.text.startswith('<?xml')
    resp2 = resp.click('Identity Providers')
    resp2 = resp2.click('New')
    idp_metadata_filename = os.path.join(os.path.dirname(__file__), 'idp_metadata.xml')
    resp2.form['metadata'] = Upload('idp_metadata.xml', open(idp_metadata_filename, 'rb').read())
    resp2 = resp2.form.submit('submit')

    resp = resp.click('Identity Providers')
    assert 'http://authentic.example.net/' in resp.text
    resp2 = resp.click(href='http-authentic.example.net-idp-saml2-metadata/', index=0)
    assert 'ns0:EntityDescriptor' in resp2.text
    resp = resp.click(href='http-authentic.example.net-idp-saml2-metadata/edit')
    resp = resp.forms[0].submit('submit')
    resp = resp.follow()

    # test that login initiates a SSO
    login_resp = app.get('/login/', status=302)
    assert login_resp.location.startswith('http://authentic.example.net/idp/saml2/sso?SAMLRequest')

    resp = resp.click(href='/backoffice/settings/identification/idp/idp/', index=0) # breadcrumb
    resp = resp.click(href='http-authentic.example.net-idp-saml2-metadata/delete')
    resp = resp.forms[0].submit() # confirm delete
    assert len(pub.cfg['idp']) == 0

    with mock.patch('wcs.qommon.misc.urlopen') as urlopen:
        idp_metadata_filename = os.path.join(os.path.dirname(__file__), 'idp_metadata.xml')
        urlopen.side_effect = lambda *args: open(idp_metadata_filename)
        resp = app.get('/backoffice/settings/identification/idp/idp/')
        resp = resp.click('Create new from remote URL')
        resp.form['metadata_url'] = 'http://authentic.example.net/idp/saml2/metadata'
        resp = resp.form.submit('submit')
        resp = resp.follow()
        assert 'http://authentic.example.net/idp/saml2/metadata' in resp.text
        assert urlopen.call_count == 1
        resp = resp.click('View')
        resp = resp.click('Update from remote URL')
        assert urlopen.call_count == 2

def test_settings_auth_password(pub):
    Role.wipe()

    pub.user_class.wipe() # makes sure there are no users
    pub.cfg['identification'] = {'methods': ['password']}
    assert pub.cfg['identification']['methods'] == ['password']
    pub.write_cfg()
    app = get_app(pub)

    resp = app.get('/backoffice/settings/identification/password/')
    resp = resp.click('Identities')
    resp = resp.forms[0].submit()

    resp = app.get('/backoffice/settings/identification/password/')
    resp = resp.click('Passwords')
    resp = resp.forms[0].submit()

    resp = app.get('/backoffice/settings/identification/password/')
    resp = resp.click('Bulk Import')
    resp = resp.forms[0].submit()

    # same with existing roles
    Role(name='blah').store()

    resp = app.get('/backoffice/settings/identification/password/')
    resp = resp.click('Bulk Import')
    resp = resp.forms[0].submit()

def test_settings_filetypes(pub):
    create_superuser(pub)
    app = login(get_app(pub))

    resp = app.get('/backoffice/settings/filetypes/')
    assert 'There are no file type defined at the moment.' in resp.text
    resp.forms[0]['label'] = 'Text files'
    resp.forms[0]['mimetypes'] = '.odt'
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/settings/filetypes/'
    resp = resp.follow()
    assert pub.cfg['filetypes'][1]['label'] == 'Text files'

    resp = resp.click('Text files')
    assert resp.forms[0]['mimetypes'].value == 'application/vnd.oasis.opendocument.text'
    resp.forms[0]['mimetypes'] = 'application/vnd.oasis.opendocument.text, .doc, .docx, .pdf'
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/settings/filetypes/'
    resp = resp.follow()
    assert 'application/msword (.' in resp.text
    assert 'application/pdf' in pub.cfg['filetypes'][1]['mimetypes']

    resp.forms[0]['label'] = 'HTML files'
    resp.forms[0]['mimetypes'] = '.html'
    resp = resp.forms[0].submit('submit')
    resp = resp.follow()
    resp = resp.click('HTML files') # go to form
    resp = resp.forms[0].submit('cancel') # and cancel
    assert resp.location == 'http://example.net/backoffice/settings/filetypes/'
    resp = resp.follow()
    assert 'HTML files' in resp.text

    resp = resp.click('HTML files') # go to form
    resp = resp.forms[0].submit('delete') # and delete
    assert resp.location == 'http://example.net/backoffice/settings/filetypes/'
    resp = resp.follow()
    assert 'HTML files' not in resp.text

def test_settings_filetypes_update(pub):
    create_superuser(pub)
    app = login(get_app(pub))

    pub.cfg['filetypes'] = {
            1: {'mimetypes': ['application/pdf', 'application/msword'],
                'label': 'Text files'}
            }
    pub.write_cfg()
    resp = app.get('/backoffice/settings/filetypes/')
    assert 'Text files' in resp.text

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [fields.FileField(
            id='1', label='1st field', type='file',
            document_type={
                'id': 1,
                'mimetypes': ['application/pdf', 'application/msword'],
                'label': 'Text files',
            })]
    formdef.store()
    assert FormDef.get(formdef.id).fields[0].document_type == {
                'id': 1,
                'mimetypes': ['application/pdf', 'application/msword'],
                'label': 'Text files',
            }

    resp = resp.click('Text files')
    resp.forms[0]['mimetypes'] = 'application/vnd.oasis.opendocument.text, .doc, .docx, .pdf'
    resp = resp.forms[0].submit('submit')
    assert 'application/pdf' in pub.cfg['filetypes'][1]['mimetypes']
    assert FormDef.get(formdef.id).fields[0].document_type == {
                'id': 1,
                'mimetypes': ['application/vnd.oasis.opendocument.text',
                              'application/msword',
                              'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
                              'application/pdf' ],
                'label': 'Text files',
            }

def test_settings_logs(pub):
    # reset logging state
    logging.shutdown()
    if os.path.exists(os.path.join(pub.app_dir, 'wcs.log')):
        os.unlink(os.path.join(pub.app_dir, 'wcs.log'))

    create_superuser(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/settings/')
    assert 'Logs' in resp.text
    resp = resp.click('Logs')
    assert '<td class="message">login</td>' in resp.text

    resp = app.get('/backoffice/settings/debug_options')
    assert resp.form['logger'].checked is True
    resp.form['logger'].checked = False
    resp = resp.form.submit()
    resp = resp.follow()
    assert 'Logs' not in resp.text

def test_settings_geolocation(pub):
    create_superuser(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/settings/')
    resp = resp.click('Geolocation')
    resp.form['default-position$latlng'].value = '1.234;-1.234'
    resp = resp.form.submit('cancel').follow()
    resp = resp.click('Geolocation')
    assert not 'value="1.234;-1.234' in resp.text
    resp.form['default-position$latlng'].value = '1.234;-1.234'
    resp = resp.form.submit().follow()
    resp = resp.click('Geolocation')
    assert 'value="1.234;-1.234' in resp.text
    pub.reload_cfg()
    assert pub.cfg['misc']['default-position'] == '1.234;-1.234'

def test_data_sources(pub):
    create_superuser(pub)
    app = login(get_app(pub))
    app.get('/backoffice/settings/data-sources/')
    # also check it's accessible from forms and workflows sections
    app.get('/backoffice/forms/data-sources/')
    app.get('/backoffice/workflows/data-sources/')

    # unknown datasource
    app.get('/backoffice/settings/data-sources/42/', status=404)
    app.get('/backoffice/forms/data-sources/42/', status=404)
    app.get('/backoffice/workflows/data-sources/42/', status=404)

def test_data_sources_new(pub):
    create_superuser(pub)
    NamedDataSource.wipe()
    app = login(get_app(pub))

    # go to the page and cancel
    resp = app.get('/backoffice/settings/data-sources/')
    resp = resp.click('New Data Source')
    resp = resp.forms[0].submit('cancel')
    assert resp.location == 'http://example.net/backoffice/settings/data-sources/'

    # go to the page and add a data source
    resp = app.get('/backoffice/settings/data-sources/')
    resp = resp.click('New Data Source')
    resp.forms[0]['name'] = 'a new data source'
    resp.forms[0]['description'] = 'description of the data source'
    resp.forms[0]['data_source$type'] = 'python'
    resp = resp.forms[0].submit('data_source$apply')
    resp.forms[0]['data_source$value'] = repr(
            [{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}])
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/settings/data-sources/'
    resp = resp.follow()
    assert 'a new data source' in resp.text
    resp = resp.click('a new data source')
    assert 'Data Source - a new data source' in resp.text
    resp = resp.click('Edit')
    assert 'Edit Data Source' in resp.text

    assert NamedDataSource.get(1).name == 'a new data source'
    assert NamedDataSource.get(1).description == 'description of the data source'

    # add a second one
    resp = app.get('/backoffice/settings/data-sources/')
    resp = resp.click('New Data Source')
    resp.forms[0]['name'] = 'an other data source'
    resp.forms[0]['description'] = 'description of the data source'
    resp.forms[0]['data_source$type'] = 'python'
    resp = resp.forms[0].submit('data_source$apply')
    resp.forms[0]['data_source$value'] = repr(
            [{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}])
    resp = resp.forms[0].submit('submit')

    assert NamedDataSource.count() == 2

def test_data_sources_view(pub):
    create_superuser(pub)
    NamedDataSource.wipe()

    data_source = NamedDataSource(name='foobar')
    app = login(get_app(pub))

    data_source.data_source = {'type': 'formula', 'value': '[]'}
    data_source.store()
    resp = app.get('/backoffice/settings/data-sources/%s/' % data_source.id)
    assert 'Type of source: Python Expression' in resp.text
    assert 'Python Expression' in resp.text
    assert not 'Preview' in resp.text

    data_source.data_source = {'type': 'formula', 'value': '["AAA", "BBB"]'}
    data_source.store()
    resp = app.get('/backoffice/settings/data-sources/%s/' % data_source.id)
    assert 'Preview' in resp.text
    assert resp.text.count('AAA') == 3 # expression + id + text

    # check unicode
    data_source.data_source = {'type': 'formula',
            'value': repr([('Y', 'Domicilié'), ('N', u'Pas domicilié')])}
    data_source.store()
    resp = app.get('/backoffice/settings/data-sources/%s/' % data_source.id)
    assert 'Preview' in resp.text
    if six.PY2:
        assert 'error: not a string' in resp.text
    else:
        assert 'Domicilié' in resp.text

    # check json
    json_file_path = os.path.join(pub.app_dir, 'test.json')
    json_file = open(json_file_path, 'w')
    json.dump({'data': [{'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]}, json_file)
    json_file.close()

    data_source.data_source = {'type': 'json', 'value': 'file://%s' % json_file_path}
    data_source.store()
    with HttpRequestsMocking() as http_requests:
        resp = app.get('/backoffice/settings/data-sources/%s/' % data_source.id)
    assert 'Preview' in resp.text
    assert 'foo' in resp.text

    data_source.data_source = {'type': 'formula', 'value': '[str(x) for x in range(100)]'}
    data_source.store()
    resp = app.get('/backoffice/settings/data-sources/%s/' % data_source.id)
    assert 'Preview' in resp.text
    assert resp.text.count('<li>') < 100
    assert '<li>...</li>' in resp.text

    data_source.data_source = {'type': 'formula', 'value': repr([
        {'id': 'a', 'text': 'BBB', 'foo': 'bar1'},
        {'id': 'b', 'text': 'BBB', 'foo': 'bar2'},
        ])}
    data_source.store()
    resp = app.get('/backoffice/settings/data-sources/%s/' % data_source.id)
    assert 'Preview' in resp.text
    assert 'Additional keys are available: foo' in resp.text

    # check formdef listing
    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'test data source'
    formdef.fields = [
        fields.ItemField(id='1', label='item', data_source={'type': data_source.slug})
    ]
    formdef.store()

    resp = app.get('/backoffice/settings/data-sources/%s/' % data_source.id)
    assert 'Usage in forms' in resp.text
    assert '/backoffice/forms/%s/' % formdef.id in resp.text

    # additional formdef types
    user_formdef = UserFieldsFormDef(pub)
    user_formdef.fields.append(
            fields.ItemField(id='1', type='item', label='item', data_source={'type': data_source.slug}))
    user_formdef.store()

    from wcs.workflows import WorkflowVariablesFieldsFormDef
    Workflow.wipe()
    workflow = Workflow(name='Workflow One')
    workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow)
    workflow.variables_formdef.fields.append(
            fields.ItemField(id='1', type='idem', label='item', data_source={'type': data_source.slug}))
    workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
    workflow.backoffice_fields_formdef.fields.append(
            fields.ItemField(id='1', type='item', label='item', data_source={'type': data_source.slug}))

    from wcs.wf.form import FormWorkflowStatusItem, WorkflowFormFieldsFormDef
    baz_status = workflow.add_status(name='baz')
    display_form = FormWorkflowStatusItem()
    display_form.id = '_x'
    display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
    display_form.formdef.fields.append(
            fields.ItemField(id='1', type='item', label='item', data_source={'type': data_source.slug}))
    baz_status.items.append(display_form)
    display_form.parent = baz_status

    workflow.store()

    carddef = CardDef()
    carddef.name = 'Baz'
    carddef.fields = [
        fields.ItemField(id='1', label='item', data_source={'type': data_source.slug})
    ]
    carddef.store()

    resp = app.get('/backoffice/settings/data-sources/%s/' % data_source.id)
    assert 'Usage in forms' in resp.text
    assert '/backoffice/forms/%s/' % formdef.id in resp.text
    assert '/backoffice/workflows/%s/backoffice-fields/fields/' % workflow.id in resp.text
    assert '/backoffice/workflows/%s/variables/fields/' % workflow.id in resp.text
    assert '/backoffice/workflows/%s/status/1/items/_x/fields/' % workflow.id in resp.text
    assert '/backoffice/settings/users/fields/' in resp.text
    assert '/backoffice/cards/%s/' % carddef.id in resp.text

    # cleanup
    user_formdef = UserFieldsFormDef(pub)
    user_formdef.fields = []
    user_formdef.store()
    Workflow.wipe()
    CardDef.wipe()


def test_data_sources_edit(pub):
    create_superuser(pub)
    NamedDataSource.wipe()
    data_source = NamedDataSource(name='foobar')
    data_source.data_source = {'type': 'formula', 'value': '[]'}
    data_source.store()

    FormDef.wipe()
    app = login(get_app(pub))
    resp = app.get('/backoffice/settings/data-sources/1/')

    resp = resp.click(href='edit')
    assert resp.forms[0]['name'].value == 'foobar'
    resp.forms[0]['description'] = 'data source description'
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/settings/data-sources/1/'
    resp = resp.follow()

    assert NamedDataSource.get(1).description == 'data source description'

def test_data_sources_edit_duplicate_name(pub):
    create_superuser(pub)
    NamedDataSource.wipe()
    data_source = NamedDataSource(name='foobar')
    data_source.data_source = {'type': 'formula', 'value': '[]'}
    data_source.store()
    data_source = NamedDataSource(name='foobar2')
    data_source.data_source = {'type': 'formula', 'value': '[]'}
    data_source.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/settings/data-sources/1/')
    resp = resp.click(href='edit')
    assert resp.forms[0]['name'].value == 'foobar'
    resp.forms[0]['name'] = 'foobar2'
    resp = resp.forms[0].submit('submit')
    assert 'This name is already used' in resp.text

    resp = resp.forms[0].submit('cancel')
    assert resp.location == 'http://example.net/backoffice/settings/data-sources/1/'

def test_data_sources_delete(pub):
    create_superuser(pub)
    NamedDataSource.wipe()
    category = NamedDataSource(name='foobar')
    category.store()

    FormDef.wipe()
    app = login(get_app(pub))
    resp = app.get('/backoffice/settings/data-sources/1/')

    resp = resp.click(href='delete')
    resp = resp.forms[0].submit('cancel')
    assert resp.location == 'http://example.net/backoffice/settings/data-sources/'
    assert NamedDataSource.count() == 1

    resp = app.get('/backoffice/settings/data-sources/1/')
    resp = resp.click(href='delete')
    resp = resp.forms[0].submit()
    assert resp.location == 'http://example.net/backoffice/settings/data-sources/'
    resp = resp.follow()
    assert NamedDataSource.count() == 0

def test_data_sources_in_use_delete(pub):
    create_superuser(pub)
    NamedDataSource.wipe()
    category = NamedDataSource(name='foobar')
    category.store()

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [
        fields.ItemField(id='0', label='string', type='item',
                data_source={'type': 'foobar'}),
    ]
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/settings/data-sources/1/')
    resp = resp.click(href='delete')
    assert 'This datasource is still used, it cannot be deleted.' in resp.text
    assert 'delete-button' not in resp.text

    formdef.fields = []
    formdef.store()
    resp = app.get('/backoffice/settings/data-sources/1/')
    resp = resp.click(href='delete')
    assert 'delete-button' in resp.text

def test_data_sources_edit_slug(pub):
    create_superuser(pub)
    NamedDataSource.wipe()
    data_source = NamedDataSource(name='foobar')
    data_source.data_source = {'type': 'formula', 'value': '[]'}
    data_source.store()
    assert NamedDataSource.get(1).slug == 'foobar'

    FormDef.wipe()
    app = login(get_app(pub))

    resp = app.get('/backoffice/settings/data-sources/1/')
    resp = resp.click(href='edit')
    assert resp.forms[0]['name'].value == 'foobar'
    resp.forms[0]['slug'] = 'foo_bar'
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/settings/data-sources/1/'
    assert NamedDataSource.get(1).slug == 'foo_bar'

    data_source = NamedDataSource(name='barfoo')
    data_source.data_source = {'type': 'formula', 'value': '[]'}
    data_source.store()

    resp = app.get('/backoffice/settings/data-sources/1/')
    resp = resp.click(href='edit')
    assert resp.forms[0]['name'].value == 'foobar'
    resp.forms[0]['slug'] = 'barfoo'
    resp = resp.forms[0].submit('submit')
    assert 'This value is already used' in resp.text

    resp.forms[0]['slug'] = 'foobar'
    resp = resp.forms[0].submit('submit')
    assert resp.location == 'http://example.net/backoffice/settings/data-sources/1/'

def test_data_sources_in_use_edit_slug(pub):
    create_superuser(pub)
    NamedDataSource.wipe()
    data_source = NamedDataSource(name='foobar')
    data_source.data_source = {'type': 'formula', 'value': '[]'}
    data_source.store()
    assert NamedDataSource.get(1).slug == 'foobar'

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = [
        fields.ItemField(id='0', label='string', type='item',
                data_source={'type': 'foobar'}),
    ]
    formdef.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/settings/data-sources/1/')
    resp = resp.click(href='edit')
    assert 'form_slug' not in resp.form.fields
    resp = resp.form.submit('submit')

    formdef.fields = []
    formdef.store()
    resp = app.get('/backoffice/settings/data-sources/1/')
    resp = resp.click(href='edit')
    assert 'form_slug' in resp.text
    resp = resp.form.submit('submit')

def test_wscalls_new(pub):
    create_superuser(pub)
    NamedWsCall.wipe()
    app = login(get_app(pub))

    # go to the page and cancel
    resp = app.get('/backoffice/settings/wscalls/')
    resp = resp.click('New webservice call')
    resp = resp.forms[0].submit('cancel')
    assert resp.location == 'http://example.net/backoffice/settings/wscalls/'

    # go to the page and add a webservice call
    resp = app.get('/backoffice/settings/wscalls/')
    resp = resp.click('New webservice call')
    resp.form['name'] = 'a new webservice call'
    resp.form['description'] = 'description'
    resp.form['request$url'] = 'http://remote.example.net/json'
    resp = resp.form.submit('submit')
    assert resp.location == 'http://example.net/backoffice/settings/wscalls/'
    resp = resp.follow()
    assert 'a new webservice call' in resp.text
    resp = resp.click('a new webservice call')
    assert 'Webservice Call - a new webservice call' in resp.text
    resp = resp.click('Edit')
    assert 'Edit webservice call' in resp.text

    assert NamedWsCall.get('a_new_webservice_call').name == 'a new webservice call'

def test_wscalls_view(pub):
    create_superuser(pub)
    NamedWsCall.wipe()

    wscall = NamedWsCall(name='xxx')
    wscall.description = 'description'
    wscall.request = {
            'url': 'http://remote.example.net/json',
            'request_signature_key': 'xxx',
            'qs_data': {'a': 'b'},
            'method': 'POST',
            'post_data': {'c': 'd'},
    }
    wscall.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/settings/wscalls/%s/' % wscall.id)
    assert 'http://remote.example.net/json' in resp.text

def test_wscalls_edit(pub):
    test_wscalls_view(pub)

    app = login(get_app(pub))

    resp = app.get('/backoffice/settings/wscalls/xxx/')
    resp = resp.click(href='edit')
    assert resp.form['name'].value == 'xxx'
    assert 'slug' in resp.form.fields
    resp.form['description'] = 'bla bla bla'
    resp = resp.form.submit('submit')
    assert resp.location == 'http://example.net/backoffice/settings/wscalls/xxx/'
    resp = resp.follow()

    assert NamedWsCall.get('xxx').description == 'bla bla bla'

    resp = app.get('/backoffice/settings/wscalls/xxx/')
    resp = resp.click(href='edit')
    assert resp.form['name'].value == 'xxx'
    assert 'slug' in resp.form.fields
    resp.form['slug'] = 'yyy'
    resp = resp.form.submit('submit')
    assert resp.location == 'http://example.net/backoffice/settings/wscalls/yyy/'


def test_wscalls_delete(pub):
    test_wscalls_view(pub)
    assert NamedWsCall.count() == 1

    app = login(get_app(pub))

    resp = app.get('/backoffice/settings/wscalls/xxx/')
    resp = resp.click(href='delete')
    resp = resp.form.submit('cancel')
    assert resp.location == 'http://example.net/backoffice/settings/wscalls/'

    resp = app.get('/backoffice/settings/wscalls/xxx/')
    resp = resp.click(href='delete')
    resp = resp.form.submit('submit')
    assert resp.location == 'http://example.net/backoffice/settings/wscalls/'
    assert NamedWsCall.count() == 0

def test_settings_permissions(pub):
    create_superuser(pub)
    role1 = create_role()
    role1.name = 'foobar1'
    role1.store()
    role2 = Role(name='foobar2')
    role2.store()
    role3 = Role(name='foobar3')
    role3.store()

    app = login(get_app(pub))
    resp = app.get('/backoffice/settings/admin-permissions')
    # assert all first checkboxes are checked
    assert resp.forms[0]['permissions$c-0-0'].checked
    assert resp.forms[0]['permissions$c-1-0'].checked
    assert resp.forms[0]['permissions$c-2-0'].checked

    role2.allows_backoffice_access = False
    role2.store()
    resp = app.get('/backoffice/settings/admin-permissions')
    assert resp.forms[0]['permissions$c-0-0'].checked
    assert not resp.forms[0]['permissions$c-1-0'].checked
    assert resp.forms[0]['permissions$c-2-0'].checked

    resp.forms[0]['permissions$c-0-0'].checked = False
    resp.forms[0]['permissions$c-1-0'].checked = True
    resp = resp.forms[0].submit()
    assert Role.get(role1.id).allows_backoffice_access is False
    assert Role.get(role2.id).allows_backoffice_access is True

    # give some roles access to the forms workshop (2nd checkbox) and to the
    # workflows workshop (4th)
    resp = app.get('/backoffice/settings/admin-permissions')
    resp.forms[0]['permissions$c-1-1'].checked = True
    resp.forms[0]['permissions$c-2-1'].checked = True
    resp.forms[0]['permissions$c-2-3'].checked = True
    resp = resp.forms[0].submit()
    pub.reload_cfg()
    assert set(pub.cfg['admin-permissions']['forms']) == set([role2.id, role3.id])
    assert set(pub.cfg['admin-permissions']['workflows']) == set([role3.id])

    # remove accesses
    resp = app.get('/backoffice/settings/admin-permissions')
    resp.forms[0]['permissions$c-1-1'].checked = False
    resp.forms[0]['permissions$c-2-1'].checked = False
    resp.forms[0]['permissions$c-2-3'].checked = False
    resp = resp.forms[0].submit()
    pub.reload_cfg()
    assert pub.cfg['admin-permissions']['forms'] == []
    assert pub.cfg['admin-permissions']['workflows'] == []

def test_settings_theme_preview(pub):
    create_superuser(pub)

    FormDef.wipe()
    formdef = FormDef()
    formdef.name = 'form title'
    formdef.fields = []
    formdef.store()

    app = login(get_app(pub))
    assert not 'alto/wcs.css' in app.get('/').text
    resp = app.get('/backoffice/settings/themes')
    assert resp.form['theme'].value in ('default', 'django')

    # visit theme preview
    resp = resp.click(href='theme_preview/alto/')
    assert 'alto/wcs.css' in resp.text

    # get into a form, making sure we are kept in theme preview
    resp = resp.click('form title')
    assert 'alto/wcs.css' in resp.text

    # verify submits are not allowed
    resp = resp.form.submit('submit')
    assert "The theme preview doesn't support this." in resp.text

def test_settings_theme_download_upload(pub):
    create_superuser(pub)

    # download existing theme
    app = login(get_app(pub))
    resp = app.get('/backoffice/settings/themes')
    resp = resp.click('download', index=0)
    assert resp.headers['content-type'] == 'application/zip'
    zip_content = BytesIO(resp.body)
    zipf = zipfile.ZipFile(zip_content, 'a')
    filelist = zipf.namelist()
    assert 'alto/icon.png' in filelist
    assert 'alto/desc.xml' in filelist
    assert 'alto/template.ezt' in filelist
    assert 'alto/wcs.css' in filelist

    # modify it
    zipf.writestr('alto/foobar.txt', 'XXX')
    zipf.close()

    # upload it
    resp = app.get('/backoffice/settings/themes')
    resp = resp.click('Install New Theme')
    resp.form['file'] = Upload('alto-modified.zip', zip_content.getvalue())
    resp = resp.form.submit()
    assert os.path.exists(os.path.join(pub.app_dir, 'themes/alto/foobar.txt'))

    assert app.get('/themes/alto/foobar.txt').text == 'XXX'
    assert 'Directory listing denied' in app.get('/themes/alto/', status=200).text

    assert app.get('/themes/alto/plop', status=404)
    assert app.get('/themes/alto/../', status=404)
    assert app.get('/themes/xxx/../', status=404)

def test_postgresql_settings(pub):
    if not pub.is_using_postgresql():
        pytest.skip('this requires SQL')
        return

    create_superuser(pub)

    database = pub.cfg['postgresql']['database']
    assert pub.cfg['postgresql'].get('port') == None

    app = login(get_app(pub))
    resp = app.get('/backoffice/settings/postgresql')
    assert resp.form['database'].value == database
    assert resp.form['port'].value == ''
    resp = resp.form.submit()
    assert pub.cfg['postgresql']['port'] == None

    pub.cfg['postgresql']['port'] = 5432
    pub.write_cfg()
    resp = app.get('/backoffice/settings/postgresql')
    assert resp.form['port'].value == '5432'
    resp = resp.form.submit()
    assert pub.cfg['postgresql']['port'] == 5432

    resp = app.get('/backoffice/settings/postgresql')
    resp.form['port'] = ''
    resp = resp.form.submit()
    assert pub.cfg['postgresql']['port'] == None

    pub.cfg['postgresql']['port'] = '5432' # from an old convert-to-sql (before #10170)
    pub.write_cfg()
    resp = app.get('/backoffice/settings/postgresql')
    assert resp.form['port'].value == '5432'
    resp = resp.form.submit()
    assert pub.cfg['postgresql']['port'] == 5432

def test_studio_home(pub, studio):
    create_superuser(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/')
    assert 'studio' in resp.text
    resp = app.get('/backoffice/studio/')
    assert '../forms/' in resp.text
    assert '../cards/' in resp.text
    assert '../workflows/' in resp.text

    pub.cfg['admin-permissions'] = {}
    for part in ('forms', 'cards', 'workflows'):
        # check section link are not displayed if user has no access right
        pub.cfg['admin-permissions'].update({part: ['x']})  #  block access
        pub.write_cfg()
        if part != 'workflows':
            resp = app.get('/backoffice/studio/')
            assert '../%s/' % part not in resp.text
        else:
            resp = app.get('/backoffice/studio/', status=403)  # totally closed

    resp = app.get('/backoffice/')
    assert 'studio' not in resp.text

def test_studio_workflows(pub, studio):
    create_superuser(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/workflows/')
    resp = resp.click(r'Default \(cards\)')
    assert 'status/recorded/' in resp.text
    assert 'status/deleted/' in resp.text
    assert 'This is the default workflow,' in resp.text

def test_cards_new(pub, studio):
    CardDef.wipe()
    create_superuser(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/cards/')
    resp = resp.click('New Card Model')
    resp.form['name'] = 'card title'
    resp = resp.form.submit()
    assert resp.location == 'http://example.net/backoffice/cards/1/'
    resp = resp.follow()
    assert '<h2>card title' in resp.text
    assert CardDef.get(1).workflow_id is None
    assert CardDef.get(1).disabled is False

def test_cards_duplicate(pub, studio):
    test_cards_new(pub, studio)
    app = login(get_app(pub))
    resp = app.get('http://example.net/backoffice/cards/1/')
    resp = resp.click('Duplicate')
    assert CardDef.get(2).name == 'card title (copy)'
    assert CardDef.get(2).disabled is False

def test_card_workflow_change(pub, studio):
    Workflow.wipe()
    workflow = Workflow(name='Workflow Two')
    workflow.add_status('plop')
    workflow.store()

    CardDef.wipe()
    create_superuser(pub)
    app = login(get_app(pub))
    resp = app.get('/backoffice/cards/')
    resp = resp.click('New Card Model')
    resp.form['name'] = 'card title'
    resp = resp.form.submit()
    resp = resp.follow()
    resp = resp.click(href='workflow', index=1)
    assert resp.form['workflow_id'].options[0][2] == 'Default (cards)'
    resp = resp.form.submit('submit').follow()
    assert CardDef.select()[0].workflow_id is None

    carddata = CardDef.select()[0].data_class()()
    carddata.status = 'wf-recorded'
    carddata.store()

    resp = resp.click(href='workflow', index=1)
    resp.form['workflow_id'] = '%s' % workflow.id
    resp = resp.form.submit('submit')
    assert resp.location == 'http://example.net/backoffice/cards/1/workflow-status-remapping?new=%s' % workflow.id
    resp = resp.follow()
    resp = resp.form.submit('submit').follow()

    resp = resp.click(href='workflow', index=1)
    resp.form['workflow_id'] = ''
    resp = resp.form.submit('submit')
    assert resp.location == 'http://example.net/backoffice/cards/1/workflow-status-remapping?new=%s' % '_carddef_default'
    resp = resp.follow()
    resp = resp.form.submit('submit').follow()
