Projet

Général

Profil

0002-misc-split-some-tests.patch

Lauréline Guérin, 26 février 2021 11:00

Télécharger (285 ko)

Voir les différences:

Subject: [PATCH 2/4] misc: split some tests

 tests/api/test_access.py      |  350 ++++
 tests/api/test_all.py         | 3667 +--------------------------------
 tests/api/test_carddef.py     |  184 ++
 tests/api/test_category.py    |  245 +++
 tests/api/test_custom_view.py |  315 +++
 tests/api/test_formdata.py    | 1285 ++++++++++++
 tests/api/test_formdef.py     |  853 ++++++++
 tests/api/test_user.py        |  457 ++++
 tests/api/test_utils.py       |    3 +-
 tests/api/test_workflow.py    |  387 ++++
 tests/api/utils.py            |   26 +
 11 files changed, 4131 insertions(+), 3641 deletions(-)
 create mode 100644 tests/api/test_access.py
 create mode 100644 tests/api/test_carddef.py
 create mode 100644 tests/api/test_category.py
 create mode 100644 tests/api/test_custom_view.py
 create mode 100644 tests/api/test_formdata.py
 create mode 100644 tests/api/test_formdef.py
 create mode 100644 tests/api/test_user.py
 create mode 100644 tests/api/test_workflow.py
 create mode 100644 tests/api/utils.py
tests/api/test_access.py
1
# -*- coding: utf-8 -*-
2

  
3
import base64
4
import datetime
5
import hashlib
6
import hmac
7
import os
8
import shutil
9

  
10
import pytest
11
from django.utils.encoding import force_bytes
12
from django.utils.six.moves.urllib import parse as urllib
13
from quixote import get_publisher
14
from utilities import clean_temporary_pub, create_temporary_pub, get_app, login
15

  
16
from wcs.api_utils import get_secret_and_orig, is_url_signed, sign_url
17
from wcs.qommon.errors import AccessForbiddenError
18
from wcs.qommon.http_request import HTTPRequest
19
from wcs.qommon.ident.password_accounts import PasswordAccount
20
from wcs.roles import Role
21

  
22

  
23
def pytest_generate_tests(metafunc):
24
    if 'pub' in metafunc.fixturenames:
25
        metafunc.parametrize('pub', ['pickle', 'sql'], indirect=True)
26

  
27

  
28
@pytest.fixture
29
def pub(request, emails):
30
    pub = create_temporary_pub(sql_mode=(request.param == 'sql'))
31

  
32
    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
33
    pub.set_app_dir(req)
34
    pub.cfg['identification'] = {'methods': ['password']}
35
    pub.cfg['language'] = {'language': 'en'}
36
    pub.write_cfg()
37

  
38
    open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w').write(
39
        '''\
40
[api-secrets]
41
coucou = 1234
42
'''
43
    )
44

  
45
    return pub
46

  
47

  
48
def teardown_module(module):
49
    clean_temporary_pub()
50

  
51

  
52
@pytest.fixture
53
def local_user():
54
    get_publisher().user_class.wipe()
55
    user = get_publisher().user_class()
56
    user.name = 'Jean Darmette'
57
    user.email = 'jean.darmette@triffouilis.fr'
58
    user.name_identifiers = ['0123456789']
59
    user.store()
60
    return user
61

  
62

  
63
@pytest.fixture
64
def admin_user():
65
    get_publisher().user_class.wipe()
66
    user = get_publisher().user_class()
67
    user.name = 'John Doe Admin'
68
    user.email = 'john.doe@example.com'
69
    user.name_identifiers = ['0123456789']
70
    user.is_admin = True
71
    user.store()
72

  
73
    account = PasswordAccount(id='admin')
74
    account.set_password('admin')
75
    account.user_id = user.id
76
    account.store()
77

  
78
    return user
79

  
80

  
81
@pytest.fixture(params=['sql', 'pickle'])
82
def no_request_pub(request):
83
    pub = create_temporary_pub(sql_mode=bool(request.param == 'sql'))
84
    pub.app_dir = os.path.join(pub.APP_DIR, 'example.net')
85
    pub.set_config()
86

  
87
    open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w').write(
88
        '''
89
[wscall-secrets]
90
api.example.com = 1234
91
'''
92
    )
93
    return pub
94

  
95

  
96
def test_get_secret_and_orig(no_request_pub):
97
    secret, orig = get_secret_and_orig('https://api.example.com/endpoint/')
98
    assert secret == '1234'
99
    assert orig == 'example.net'
100

  
101

  
102
def test_user_page_redirect(pub):
103
    output = get_app(pub).get('/user')
104
    assert output.headers.get('location') == 'http://example.net/myspace/'
105

  
106

  
107
def test_user_page_error(pub):
108
    # check we get json as output for errors
109
    output = get_app(pub).get('/api/user/', status=403)
110
    assert output.json['err_desc'] == 'no user specified'
111

  
112

  
113
def test_user_page_error_when_json_and_no_user(pub):
114
    output = get_app(pub).get('/api/user/?format=json', status=403)
115
    assert output.json['err_desc'] == 'no user specified'
116

  
117

  
118
def test_get_user_from_api_query_string_error_missing_orig(pub):
119
    output = get_app(pub).get('/api/user/?format=json&signature=xxx', status=403)
120
    assert output.json['err_desc'] == 'missing/multiple orig field'
121

  
122

  
123
def test_get_user_from_api_query_string_error_invalid_orig(pub):
124
    output = get_app(pub).get('/api/user/?format=json&orig=coin&signature=xxx', status=403)
125
    assert output.json['err_desc'] == 'invalid orig'
126

  
127

  
128
def test_get_user_from_api_query_string_error_missing_algo(pub):
129
    output = get_app(pub).get('/api/user/?format=json&orig=coucou&signature=xxx', status=403)
130
    assert output.json['err_desc'] == 'missing/multiple algo field'
131

  
132

  
133
def test_get_user_from_api_query_string_error_invalid_algo(pub):
134
    output = get_app(pub).get('/api/user/?format=json&orig=coucou&signature=xxx&algo=coin', status=403)
135
    assert output.json['err_desc'] == 'invalid algo'
136
    output = get_app(pub).get(
137
        '/api/user/?format=json&orig=coucou&signature=xxx&algo=__getattribute__', status=403
138
    )
139
    assert output.json['err_desc'] == 'invalid algo'
140

  
141

  
142
def test_get_user_from_api_query_string_error_invalid_signature(pub):
143
    output = get_app(pub).get('/api/user/?format=json&orig=coucou&signature=xxx&algo=sha1', status=403)
144
    assert output.json['err_desc'] == 'invalid signature'
145

  
146

  
147
def test_get_user_from_api_query_string_error_missing_timestamp(pub):
148
    signature = urllib.quote(
149
        base64.b64encode(hmac.new(b'1234', b'format=json&orig=coucou&algo=sha1', hashlib.sha1).digest())
150
    )
151
    output = get_app(pub).get(
152
        '/api/user/?format=json&orig=coucou&algo=sha1&signature=%s' % signature, status=403
153
    )
154
    assert output.json['err_desc'] == 'missing/multiple timestamp field'
155

  
156

  
157
def test_get_user_from_api_query_string_error_missing_email(pub):
158
    timestamp = datetime.datetime.utcnow().isoformat()[:19] + 'Z'
159
    query = 'format=json&orig=coucou&algo=sha1&timestamp=' + timestamp
160
    signature = urllib.quote(base64.b64encode(hmac.new(b'1234', force_bytes(query), hashlib.sha1).digest()))
161
    output = get_app(pub).get('/api/user/?%s&signature=%s' % (query, signature), status=403)
162
    assert output.json['err_desc'] == 'no user specified'
163

  
164

  
165
def test_get_user_from_api_query_string_error_unknown_nameid(pub):
166
    timestamp = datetime.datetime.utcnow().isoformat()[:19] + 'Z'
167
    query = 'format=json&orig=coucou&algo=sha1&NameID=xxx&timestamp=' + timestamp
168
    signature = urllib.quote(base64.b64encode(hmac.new(b'1234', force_bytes(query), hashlib.sha1).digest()))
169
    output = get_app(pub).get('/api/user/?%s&signature=%s' % (query, signature), status=403)
170
    assert output.json['err_desc'] == 'unknown NameID'
171

  
172

  
173
def test_get_user_from_api_query_string_error_missing_email_valid_endpoint(pub):
174
    # check it's ok to sign an URL without specifiying an user if the endpoint
175
    # works fine without user.
176
    timestamp = datetime.datetime.utcnow().isoformat()[:19] + 'Z'
177
    query = 'format=json&orig=coucou&algo=sha1&timestamp=' + timestamp
178
    signature = urllib.quote(base64.b64encode(hmac.new(b'1234', force_bytes(query), hashlib.sha1).digest()))
179
    output = get_app(pub).get('/categories?%s&signature=%s' % (query, signature))
180
    assert output.json == {'data': []}
181
    output = get_app(pub).get('/json?%s&signature=%s' % (query, signature))
182
    assert output.json == {'err': 0, 'data': []}
183

  
184

  
185
def test_get_user_from_api_query_string_error_unknown_nameid_valid_endpoint(pub):
186
    # check the categories and forms endpoints accept an unknown NameID
187
    timestamp = datetime.datetime.utcnow().isoformat()[:19] + 'Z'
188
    query = 'format=json&NameID=xxx&orig=coucou&algo=sha1&timestamp=' + timestamp
189
    signature = urllib.quote(base64.b64encode(hmac.new(b'1234', force_bytes(query), hashlib.sha1).digest()))
190
    output = get_app(pub).get('/categories?%s&signature=%s' % (query, signature))
191
    assert output.json == {'data': []}
192
    output = get_app(pub).get('/json?%s&signature=%s' % (query, signature))
193
    assert output.json == {'err': 0, 'data': []}
194

  
195

  
196
def test_get_user_from_api_query_string_error_success_sha1(pub, local_user):
197
    timestamp = datetime.datetime.utcnow().isoformat()[:19] + 'Z'
198
    query = (
199
        'format=json&orig=coucou&algo=sha1&email='
200
        + urllib.quote(local_user.email)
201
        + '&timestamp='
202
        + timestamp
203
    )
204
    signature = urllib.quote(base64.b64encode(hmac.new(b'1234', force_bytes(query), hashlib.sha1).digest()))
205
    output = get_app(pub).get('/api/user/?%s&signature=%s' % (query, signature))
206
    assert output.json['user_display_name'] == u'Jean Darmette'
207

  
208

  
209
def test_get_user_from_api_query_string_error_invalid_signature_algo_mismatch(pub, local_user):
210
    timestamp = datetime.datetime.utcnow().isoformat()[:19] + 'Z'
211
    query = (
212
        'format=json&orig=coucou&algo=sha256&email='
213
        + urllib.quote(local_user.email)
214
        + '&timestamp='
215
        + timestamp
216
    )
217
    signature = urllib.quote(base64.b64encode(hmac.new(b'1234', force_bytes(query), hashlib.sha1).digest()))
218
    output = get_app(pub).get('/api/user/?%s&signature=%s' % (query, signature), status=403)
219
    assert output.json['err_desc'] == 'invalid signature'
220

  
221

  
222
def test_get_user_from_api_query_string_error_success_sha256(pub, local_user):
223
    timestamp = datetime.datetime.utcnow().isoformat()[:19] + 'Z'
224
    query = (
225
        'format=json&orig=coucou&algo=sha256&email='
226
        + urllib.quote(local_user.email)
227
        + '&timestamp='
228
        + timestamp
229
    )
230
    signature = urllib.quote(base64.b64encode(hmac.new(b'1234', force_bytes(query), hashlib.sha256).digest()))
231
    output = get_app(pub).get('/api/user/?%s&signature=%s' % (query, signature))
232
    assert output.json['user_display_name'] == u'Jean Darmette'
233

  
234

  
235
def test_sign_url(pub, local_user):
236
    signed_url = sign_url(
237
        'http://example.net/api/user/?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
238
        '1234',
239
    )
240
    url = signed_url[len('http://example.net') :]
241
    output = get_app(pub).get(url)
242
    assert output.json['user_display_name'] == u'Jean Darmette'
243

  
244
    # try to add something after signed url
245
    get_app(pub).get('%s&foo=bar' % url, status=403)
246

  
247
    signed_url = sign_url(
248
        'http://example.net/api/user/?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
249
        '12345',
250
    )
251
    url = signed_url[len('http://example.net') :]
252
    output = get_app(pub).get(url, status=403)
253

  
254

  
255
def test_get_user(pub, local_user):
256
    Role.wipe()
257
    role = Role(name='Foo bar')
258
    role.store()
259
    local_user.roles = [role.id]
260
    local_user.store()
261
    signed_url = sign_url(
262
        'http://example.net/api/user/?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
263
        '1234',
264
    )
265
    url = signed_url[len('http://example.net') :]
266
    output = get_app(pub).get(url)
267
    assert output.json['user_display_name'] == u'Jean Darmette'
268
    assert [x['name'] for x in output.json['user_roles']] == ['Foo bar']
269
    assert [x['slug'] for x in output.json['user_roles']] == ['foo-bar']
270

  
271

  
272
def test_api_access_from_xml_storable_object(pub, local_user, admin_user):
273
    app = login(get_app(pub))
274
    resp = app.get('/backoffice/settings/api-access/new')
275
    resp.form['name'] = 'Salut API access key'
276
    resp.form['access_identifier'] = 'salut'
277
    resp.form['access_key'] = '5678'
278
    resp = resp.form.submit('submit')
279

  
280
    Role.wipe()
281
    role = Role(name='Foo bar')
282
    role.store()
283
    local_user.roles = [role.id]
284
    local_user.store()
285
    signed_url = sign_url(
286
        'http://example.net/api/user/?format=json&orig=UNKNOWN_ACCESS&email=%s'
287
        % (urllib.quote(local_user.email)),
288
        '5678',
289
    )
290
    url = signed_url[len('http://example.net') :]
291
    output = get_app(pub).get(url, status=403)
292
    assert output.json['err_desc'] == 'invalid orig'
293

  
294
    signed_url = sign_url(
295
        'http://example.net/api/user/?format=json&orig=salut&email=%s' % (urllib.quote(local_user.email)),
296
        '5678',
297
    )
298
    url = signed_url[len('http://example.net') :]
299
    output = get_app(pub).get(url)
300
    assert output.json['user_display_name'] == u'Jean Darmette'
301

  
302

  
303
def test_is_url_signed_check_nonce(pub, local_user, freezer):
304
    ORIG = 'xxx'
305
    KEY = 'xxx'
306

  
307
    pub.site_options.add_section('api-secrets')
308
    pub.site_options.set('api-secrets', ORIG, KEY)
309
    pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
310
    # test clean_nonces do not bark when nonces directory is empty
311
    if os.path.exists(os.path.join(pub.app_dir, 'nonces')):
312
        shutil.rmtree(os.path.join(pub.app_dir, 'nonces'))
313
    pub.clean_nonces(now=0)
314
    nonce_dir = os.path.join(pub.app_dir, 'nonces')
315
    assert not os.path.exists(nonce_dir) or not os.listdir(nonce_dir)
316
    signed_url = sign_url('?format=json&orig=%s&email=%s' % (ORIG, urllib.quote(local_user.email)), KEY)
317
    req = HTTPRequest(
318
        None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net', 'QUERY_STRING': signed_url[1:]}
319
    )
320
    req.process_inputs()
321
    pub.set_app_dir(req)
322
    pub._set_request(req)
323

  
324
    assert is_url_signed()
325
    with pytest.raises(AccessForbiddenError) as exc_info:
326
        req.signed = False
327
        is_url_signed()
328
    assert exc_info.value.public_msg == 'nonce already used'
329
    # test that clean nonces works
330
    pub.clean_nonces()
331
    assert os.listdir(nonce_dir)
332

  
333
    # 80 seconds in the future, nothing should be cleaned
334
    freezer.move_to(datetime.timedelta(seconds=80))
335
    pub.clean_nonces()
336
    assert os.listdir(nonce_dir)
337

  
338
    # 90 seconds in the future, nonces should be removed
339
    freezer.move_to(datetime.timedelta(seconds=10))
340
    pub.clean_nonces()
341
    assert not os.listdir(nonce_dir)
342

  
343

  
344
def test_get_user_compat_endpoint(pub, local_user):
345
    signed_url = sign_url(
346
        'http://example.net/user?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email), '1234'
347
    )
348
    url = signed_url[len('http://example.net') :]
349
    output = get_app(pub).get(url)
350
    assert output.json['user_display_name'] == u'Jean Darmette'
tests/api/test_all.py
1 1
# -*- coding: utf-8 -*-
2 2

  
3
import pytest
4
import json
5
import shutil
6
import os
7
import hmac
8
import base64
9
import hashlib
10
import mock
11
import re
12
import datetime
13
import time
14
import json
15
import sys
16
import xml.etree.ElementTree as ET
17
import zipfile
18

  
19
from django.utils.encoding import force_bytes, force_text
20
from django.utils.six import StringIO, BytesIO
21
from django.utils.six.moves.urllib import parse as urllib
22
from django.utils.six.moves.urllib import parse as urlparse
23

  
24
from quixote import cleanup, get_publisher
25
from wcs.qommon.http_request import HTTPRequest
26
from wcs.qommon.form import PicklableUpload
27
from wcs.qommon.ident.password_accounts import PasswordAccount
28
from wcs.qommon import ods
29
from wcs.users import User
30
from wcs.roles import Role
31
from wcs.blocks import BlockDef
32
from wcs.carddef import CardDef
33
from wcs.formdef import FormDef
34
from wcs.formdata import Evolution
35
from wcs.categories import Category, CardDefCategory
36
from wcs.data_sources import NamedDataSource
37
from wcs.workflows import (
38
    Workflow,
39
    EditableWorkflowStatusItem,
40
    WorkflowBackofficeFieldsFormDef,
41
    WorkflowVariablesFieldsFormDef,
42
)
43
from wcs.wf.jump import JumpWorkflowStatusItem
44
from wcs.wf.register_comment import RegisterCommenterWorkflowStatusItem
45
from wcs import fields, qommon
46
from wcs.api_utils import sign_url, get_secret_and_orig, is_url_signed, DEFAULT_DURATION
47
from wcs.qommon.errors import AccessForbiddenError
48

  
49
from utilities import get_app, create_temporary_pub, clean_temporary_pub, login
50

  
51

  
52
def pytest_generate_tests(metafunc):
53
    if 'pub' in metafunc.fixturenames:
54
        metafunc.parametrize('pub', ['pickle', 'sql'], indirect=True)
55

  
56

  
57
@pytest.fixture
58
def pub(request, emails):
59
    pub = create_temporary_pub(sql_mode=(request.param == 'sql'))
60

  
61
    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
62
    pub.set_app_dir(req)
63
    pub.cfg['identification'] = {'methods': ['password']}
64
    pub.cfg['language'] = {'language': 'en'}
65
    pub.write_cfg()
66

  
67
    open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w').write(
68
        '''\
69
[api-secrets]
70
coucou = 1234
71
'''
72
    )
73

  
74
    return pub
75

  
76

  
77
def teardown_module(module):
78
    clean_temporary_pub()
79

  
80

  
81
@pytest.fixture
82
def local_user():
83
    get_publisher().user_class.wipe()
84
    user = get_publisher().user_class()
85
    user.name = 'Jean Darmette'
86
    user.email = 'jean.darmette@triffouilis.fr'
87
    user.name_identifiers = ['0123456789']
88
    user.store()
89
    return user
90

  
91

  
92
@pytest.fixture
93
def admin_user():
94
    get_publisher().user_class.wipe()
95
    user = get_publisher().user_class()
96
    user.name = 'John Doe Admin'
97
    user.email = 'john.doe@example.com'
98
    user.name_identifiers = ['0123456789']
99
    user.is_admin = True
100
    user.store()
101

  
102
    account = PasswordAccount(id='admin')
103
    account.set_password('admin')
104
    account.user_id = user.id
105
    account.store()
106

  
107
    return user
108

  
109

  
110
def sign_uri(uri, user=None, format='json'):
111
    timestamp = datetime.datetime.utcnow().isoformat()[:19] + 'Z'
112
    scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri)
113
    if query:
114
        query += '&'
115
    if format:
116
        query += 'format=%s&' % format
117
    query += 'orig=coucou&algo=sha256&timestamp=' + timestamp
118
    if user:
119
        query += '&email=' + urllib.quote(user.email)
120
    query += '&signature=%s' % urllib.quote(
121
        base64.b64encode(hmac.new(b'1234', force_bytes(query), hashlib.sha256).digest())
122
    )
123
    return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
124

  
125

  
126
def test_user_page_redirect(pub):
127
    output = get_app(pub).get('/user')
128
    assert output.headers.get('location') == 'http://example.net/myspace/'
129

  
130

  
131
def test_user_page_error(pub):
132
    # check we get json as output for errors
133
    output = get_app(pub).get('/api/user/', status=403)
134
    assert output.json['err_desc'] == 'no user specified'
135

  
136

  
137
def test_user_page_error_when_json_and_no_user(pub):
138
    output = get_app(pub).get('/api/user/?format=json', status=403)
139
    assert output.json['err_desc'] == 'no user specified'
140

  
141

  
142
def test_get_user_from_api_query_string_error_missing_orig(pub):
143
    output = get_app(pub).get('/api/user/?format=json&signature=xxx', status=403)
144
    assert output.json['err_desc'] == 'missing/multiple orig field'
145

  
146

  
147
def test_get_user_from_api_query_string_error_invalid_orig(pub):
148
    output = get_app(pub).get('/api/user/?format=json&orig=coin&signature=xxx', status=403)
149
    assert output.json['err_desc'] == 'invalid orig'
150

  
151

  
152
def test_get_user_from_api_query_string_error_missing_algo(pub):
153
    output = get_app(pub).get('/api/user/?format=json&orig=coucou&signature=xxx', status=403)
154
    assert output.json['err_desc'] == 'missing/multiple algo field'
155

  
156

  
157
def test_get_user_from_api_query_string_error_invalid_algo(pub):
158
    output = get_app(pub).get('/api/user/?format=json&orig=coucou&signature=xxx&algo=coin', status=403)
159
    assert output.json['err_desc'] == 'invalid algo'
160
    output = get_app(pub).get(
161
        '/api/user/?format=json&orig=coucou&signature=xxx&algo=__getattribute__', status=403
162
    )
163
    assert output.json['err_desc'] == 'invalid algo'
164

  
165

  
166
def test_get_user_from_api_query_string_error_invalid_signature(pub):
167
    output = get_app(pub).get('/api/user/?format=json&orig=coucou&signature=xxx&algo=sha1', status=403)
168
    assert output.json['err_desc'] == 'invalid signature'
169

  
170

  
171
def test_get_user_from_api_query_string_error_missing_timestamp(pub):
172
    signature = urllib.quote(
173
        base64.b64encode(hmac.new(b'1234', b'format=json&orig=coucou&algo=sha1', hashlib.sha1).digest())
174
    )
175
    output = get_app(pub).get(
176
        '/api/user/?format=json&orig=coucou&algo=sha1&signature=%s' % signature, status=403
177
    )
178
    assert output.json['err_desc'] == 'missing/multiple timestamp field'
179

  
180

  
181
def test_get_user_from_api_query_string_error_missing_email(pub):
182
    timestamp = datetime.datetime.utcnow().isoformat()[:19] + 'Z'
183
    query = 'format=json&orig=coucou&algo=sha1&timestamp=' + timestamp
184
    signature = urllib.quote(base64.b64encode(hmac.new(b'1234', force_bytes(query), hashlib.sha1).digest()))
185
    output = get_app(pub).get('/api/user/?%s&signature=%s' % (query, signature), status=403)
186
    assert output.json['err_desc'] == 'no user specified'
187

  
188

  
189
def test_get_user_from_api_query_string_error_unknown_nameid(pub):
190
    timestamp = datetime.datetime.utcnow().isoformat()[:19] + 'Z'
191
    query = 'format=json&orig=coucou&algo=sha1&NameID=xxx&timestamp=' + timestamp
192
    signature = urllib.quote(base64.b64encode(hmac.new(b'1234', force_bytes(query), hashlib.sha1).digest()))
193
    output = get_app(pub).get('/api/user/?%s&signature=%s' % (query, signature), status=403)
194
    assert output.json['err_desc'] == 'unknown NameID'
195

  
196

  
197
def test_get_user_from_api_query_string_error_missing_email_valid_endpoint(pub):
198
    # check it's ok to sign an URL without specifiying an user if the endpoint
199
    # works fine without user.
200
    timestamp = datetime.datetime.utcnow().isoformat()[:19] + 'Z'
201
    query = 'format=json&orig=coucou&algo=sha1&timestamp=' + timestamp
202
    signature = urllib.quote(base64.b64encode(hmac.new(b'1234', force_bytes(query), hashlib.sha1).digest()))
203
    output = get_app(pub).get('/categories?%s&signature=%s' % (query, signature))
204
    assert output.json == {'data': []}
205
    output = get_app(pub).get('/json?%s&signature=%s' % (query, signature))
206
    assert output.json == {'err': 0, 'data': []}
207

  
208

  
209
def test_get_user_from_api_query_string_error_unknown_nameid_valid_endpoint(pub):
210
    # check the categories and forms endpoints accept an unknown NameID
211
    timestamp = datetime.datetime.utcnow().isoformat()[:19] + 'Z'
212
    query = 'format=json&NameID=xxx&orig=coucou&algo=sha1&timestamp=' + timestamp
213
    signature = urllib.quote(base64.b64encode(hmac.new(b'1234', force_bytes(query), hashlib.sha1).digest()))
214
    output = get_app(pub).get('/categories?%s&signature=%s' % (query, signature))
215
    assert output.json == {'data': []}
216
    output = get_app(pub).get('/json?%s&signature=%s' % (query, signature))
217
    assert output.json == {'err': 0, 'data': []}
218

  
219

  
220
def test_get_user_from_api_query_string_error_success_sha1(pub, local_user):
221
    timestamp = datetime.datetime.utcnow().isoformat()[:19] + 'Z'
222
    query = (
223
        'format=json&orig=coucou&algo=sha1&email='
224
        + urllib.quote(local_user.email)
225
        + '&timestamp='
226
        + timestamp
227
    )
228
    signature = urllib.quote(base64.b64encode(hmac.new(b'1234', force_bytes(query), hashlib.sha1).digest()))
229
    output = get_app(pub).get('/api/user/?%s&signature=%s' % (query, signature))
230
    assert output.json['user_display_name'] == u'Jean Darmette'
231

  
232

  
233
def test_get_user_from_api_query_string_error_invalid_signature_algo_mismatch(pub, local_user):
234
    timestamp = datetime.datetime.utcnow().isoformat()[:19] + 'Z'
235
    query = (
236
        'format=json&orig=coucou&algo=sha256&email='
237
        + urllib.quote(local_user.email)
238
        + '&timestamp='
239
        + timestamp
240
    )
241
    signature = urllib.quote(base64.b64encode(hmac.new(b'1234', force_bytes(query), hashlib.sha1).digest()))
242
    output = get_app(pub).get('/api/user/?%s&signature=%s' % (query, signature), status=403)
243
    assert output.json['err_desc'] == 'invalid signature'
244

  
245

  
246
def test_get_user_from_api_query_string_error_success_sha256(pub, local_user):
247
    timestamp = datetime.datetime.utcnow().isoformat()[:19] + 'Z'
248
    query = (
249
        'format=json&orig=coucou&algo=sha256&email='
250
        + urllib.quote(local_user.email)
251
        + '&timestamp='
252
        + timestamp
253
    )
254
    signature = urllib.quote(base64.b64encode(hmac.new(b'1234', force_bytes(query), hashlib.sha256).digest()))
255
    output = get_app(pub).get('/api/user/?%s&signature=%s' % (query, signature))
256
    assert output.json['user_display_name'] == u'Jean Darmette'
257

  
258

  
259
def test_sign_url(pub, local_user):
260
    signed_url = sign_url(
261
        'http://example.net/api/user/?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
262
        '1234',
263
    )
264
    url = signed_url[len('http://example.net') :]
265
    output = get_app(pub).get(url)
266
    assert output.json['user_display_name'] == u'Jean Darmette'
267

  
268
    # try to add something after signed url
269
    get_app(pub).get('%s&foo=bar' % url, status=403)
270

  
271
    signed_url = sign_url(
272
        'http://example.net/api/user/?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
273
        '12345',
274
    )
275
    url = signed_url[len('http://example.net') :]
276
    output = get_app(pub).get(url, status=403)
277

  
278

  
279
def test_get_user(pub, local_user):
280
    Role.wipe()
281
    role = Role(name='Foo bar')
282
    role.store()
283
    local_user.roles = [role.id]
284
    local_user.store()
285
    signed_url = sign_url(
286
        'http://example.net/api/user/?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
287
        '1234',
288
    )
289
    url = signed_url[len('http://example.net') :]
290
    output = get_app(pub).get(url)
291
    assert output.json['user_display_name'] == u'Jean Darmette'
292
    assert [x['name'] for x in output.json['user_roles']] == ['Foo bar']
293
    assert [x['slug'] for x in output.json['user_roles']] == ['foo-bar']
294

  
295

  
296
def test_api_access_from_xml_storable_object(pub, local_user, admin_user):
297
    app = login(get_app(pub))
298
    resp = app.get('/backoffice/settings/api-access/new')
299
    resp.form['name'] = 'Salut API access key'
300
    resp.form['access_identifier'] = 'salut'
301
    resp.form['access_key'] = '5678'
302
    resp = resp.form.submit('submit')
303

  
304
    Role.wipe()
305
    role = Role(name='Foo bar')
306
    role.store()
307
    local_user.roles = [role.id]
308
    local_user.store()
309
    signed_url = sign_url(
310
        'http://example.net/api/user/?format=json&orig=UNKNOWN_ACCESS&email=%s'
311
        % (urllib.quote(local_user.email)),
312
        '5678',
313
    )
314
    url = signed_url[len('http://example.net') :]
315
    output = get_app(pub).get(url, status=403)
316
    assert output.json['err_desc'] == 'invalid orig'
317

  
318
    signed_url = sign_url(
319
        'http://example.net/api/user/?format=json&orig=salut&email=%s' % (urllib.quote(local_user.email)),
320
        '5678',
321
    )
322
    url = signed_url[len('http://example.net') :]
323
    output = get_app(pub).get(url)
324
    assert output.json['user_display_name'] == u'Jean Darmette'
325

  
326

  
327
def test_is_url_signed_check_nonce(pub, local_user, freezer):
328
    ORIG = 'xxx'
329
    KEY = 'xxx'
330

  
331
    pub.site_options.add_section('api-secrets')
332
    pub.site_options.set('api-secrets', ORIG, KEY)
333
    pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
334
    # test clean_nonces do not bark when nonces directory is empty
335
    if os.path.exists(os.path.join(pub.app_dir, 'nonces')):
336
        shutil.rmtree(os.path.join(pub.app_dir, 'nonces'))
337
    pub.clean_nonces(now=0)
338
    nonce_dir = os.path.join(pub.app_dir, 'nonces')
339
    assert not os.path.exists(nonce_dir) or not os.listdir(nonce_dir)
340
    signed_url = sign_url('?format=json&orig=%s&email=%s' % (ORIG, urllib.quote(local_user.email)), KEY)
341
    req = HTTPRequest(
342
        None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net', 'QUERY_STRING': signed_url[1:]}
343
    )
344
    req.process_inputs()
345
    pub.set_app_dir(req)
346
    pub._set_request(req)
347

  
348
    assert is_url_signed()
349
    with pytest.raises(AccessForbiddenError) as exc_info:
350
        req.signed = False
351
        is_url_signed()
352
    assert exc_info.value.public_msg == 'nonce already used'
353
    # test that clean nonces works
354
    pub.clean_nonces()
355
    assert os.listdir(nonce_dir)
356

  
357
    # 80 seconds in the future, nothing should be cleaned
358
    freezer.move_to(datetime.timedelta(seconds=80))
359
    pub.clean_nonces()
360
    assert os.listdir(nonce_dir)
361

  
362
    # 90 seconds in the future, nonces should be removed
363
    freezer.move_to(datetime.timedelta(seconds=10))
364
    pub.clean_nonces()
365
    assert not os.listdir(nonce_dir)
366

  
367

  
368
def test_get_user_compat_endpoint(pub, local_user):
369
    signed_url = sign_url(
370
        'http://example.net/user?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email), '1234'
371
    )
372
    url = signed_url[len('http://example.net') :]
373
    output = get_app(pub).get(url)
374
    assert output.json['user_display_name'] == u'Jean Darmette'
375

  
376

  
377
def test_formdef_list(pub):
378
    Role.wipe()
379
    role = Role(name='Foo bar')
380
    role.id = '14'
381
    role.store()
382

  
383
    FormDef.wipe()
384
    formdef = FormDef()
385
    formdef.name = 'test'
386
    formdef.description = 'plop'
387
    formdef.keywords = 'mobile, test'
388
    formdef.workflow_roles = {'_receiver': str(role.id)}
389
    formdef.fields = []
390
    formdef.store()
391

  
392
    # anonymous access -> 403
393
    resp1 = get_app(pub).get('/json', status=403)
394
    resp2 = get_app(pub).get('/', headers={'Accept': 'application/json'}, status=403)
395
    resp3 = get_app(pub).get('/api/formdefs/', status=403)
396

  
397
    # signed request
398
    resp1 = get_app(pub).get(sign_uri('/json'))
399
    resp2 = get_app(pub).get(sign_uri('/'), headers={'Accept': 'application/json'})
400
    resp3 = get_app(pub).get(sign_uri('/api/formdefs/'))
401
    assert resp1.json == resp2.json == resp3.json
402
    assert resp1.json['data'][0]['title'] == 'test'
403
    assert resp1.json['data'][0]['url'] == 'http://example.net/test/'
404
    assert resp1.json['data'][0]['redirection'] is False
405
    assert resp1.json['data'][0]['always_advertise'] is False
406
    assert resp1.json['data'][0]['description'] == 'plop'
407
    assert resp1.json['data'][0]['keywords'] == ['mobile', 'test']
408
    assert list(resp1.json['data'][0]['functions'].keys()) == ['_receiver']
409
    assert resp1.json['data'][0]['functions']['_receiver']['label'] == 'Recipient'
410
    assert resp1.json['data'][0]['functions']['_receiver']['role']['slug'] == role.slug
411
    assert resp1.json['data'][0]['functions']['_receiver']['role']['name'] == role.name
412
    assert 'count' not in resp1.json['data'][0]
413

  
414
    # backoffice_submission formdef : none
415
    resp1 = get_app(pub).get('/api/formdefs/?backoffice-submission=on', status=403)
416
    resp1 = get_app(pub).get(sign_uri('/api/formdefs/?backoffice-submission=on'))
417
    assert resp1.json['err'] == 0
418
    assert len(resp1.json['data']) == 0
419

  
420
    formdef.data_class().wipe()
421

  
422
    # a draft
423
    formdata = formdef.data_class()()
424
    formdata.data = {}
425
    formdata.just_created()
426
    formdata.status = 'draft'
427
    formdata.store()
428

  
429
    other_formdef = FormDef()
430
    other_formdef.name = 'test 2'
431
    other_formdef.fields = []
432
    other_formdef.store()
433
    other_formdata = other_formdef.data_class()()
434
    other_formdata.data = {}
435
    other_formdata.just_created()
436
    other_formdata.store()
437

  
438
    # formdata created:
439
    # - 1 day ago (=3*4)
440
    # - 7 days ago (=2*2)
441
    # - 29 days ago (=1*1)
442
    # - 31 days ago (=0)
443
    for days in [1, 1, 1, 7, 7, 29, 31]:
444
        formdata = formdef.data_class()()
445
        formdata.data = {}
446
        formdata.just_created()
447
        formdata.receipt_time = (datetime.datetime.now() - datetime.timedelta(days=days)).timetuple()
448
        formdata.store()
449

  
450
    resp = get_app(pub).get(sign_uri('/api/formdefs/?include-count=on'))
451
    if not pub.is_using_postgresql():
452
        assert resp.json['data'][0]['count'] == 8
453
    else:
454
        # 3*4 + 2*2 + 1*1
455
        assert resp.json['data'][0]['count'] == 17
456

  
457

  
458
def test_limited_formdef_list(pub, local_user):
459
    Role.wipe()
460
    role = Role(name='Foo bar')
461
    role.id = '14'
462
    role.store()
463

  
464
    FormDef.wipe()
465
    formdef = FormDef()
466
    formdef.name = 'test'
467
    formdef.description = 'plop'
468
    formdef.workflow_roles = {'_receiver': str(role.id)}
469
    formdef.fields = []
470
    formdef.store()
471

  
472
    resp = get_app(pub).get(sign_uri('/api/formdefs/'))
473
    assert resp.json['err'] == 0
474
    assert len(resp.json['data']) == 1
475
    assert resp.json['data'][0]['authentication_required'] is False
476
    # not present in backoffice-submission formdefs
477
    resp = get_app(pub).get(sign_uri('/api/formdefs/?backoffice-submission=on'))
478
    assert resp.json['err'] == 0
479
    assert len(resp.json['data']) == 0
480

  
481
    # check it's not advertised
482
    formdef.roles = [role.id]
483
    formdef.store()
484
    resp = get_app(pub).get(sign_uri('/api/formdefs/'))
485
    resp2 = get_app(pub).get(sign_uri('/api/formdefs/?NameID='))
486
    resp3 = get_app(pub).get(sign_uri('/api/formdefs/?NameID=XXX'))
487
    resp4 = get_app(pub).get(sign_uri('/api/formdefs/?NameID=%s' % local_user.name_identifiers[0]))
488
    assert resp.json['err'] == 0
489
    assert len(resp.json['data']) == 1  # advertised in naked calls (as done from combo)
490
    assert len(resp2.json['data']) == 0  # not advertised otherwise
491
    assert resp2.json == resp3.json == resp4.json
492
    # still not present in backoffice-submission formdefs
493
    resp = get_app(pub).get(sign_uri('/api/formdefs/?backoffice-submission=on'))
494
    assert resp.json['err'] == 0
495
    assert len(resp.json['data']) == 0
496

  
497
    # unless user has correct roles
498
    local_user.roles = [role.id]
499
    local_user.store()
500
    resp = get_app(pub).get(sign_uri('/api/formdefs/?NameID=%s' % local_user.name_identifiers[0]))
501
    assert resp.json['err'] == 0
502
    assert len(resp.json['data']) == 1
503

  
504
    local_user.roles = []
505
    local_user.store()
506

  
507
    # check it's also included in anonymous/signed calls, but marked for
508
    # authentication
509
    resp = get_app(pub).get(sign_uri('/api/formdefs/'))
510
    assert resp.json['data'][0]
511
    assert resp.json['data'][0]['authentication_required'] is True
512

  
513
    # check it's advertised
514
    formdef.always_advertise = True
515
    formdef.store()
516
    resp = get_app(pub).get(sign_uri('/api/formdefs/'))
517
    resp2 = get_app(pub).get(sign_uri('/api/formdefs/?NameID='))
518
    resp3 = get_app(pub).get(sign_uri('/api/formdefs/?NameID=XXX'))
519
    resp4 = get_app(pub).get(sign_uri('/api/formdefs/?NameID=%s' % local_user.name_identifiers[0]))
520
    assert resp.json['err'] == 0
521
    assert len(resp.json['data']) == 1
522
    assert resp.json['data'][0]['authentication_required']
523
    assert resp.json == resp2.json == resp3.json == resp4.json
524

  
525
    formdef.required_authentication_contexts = ['fedict']
526
    formdef.store()
527
    resp = get_app(pub).get(sign_uri('/api/formdefs/'))
528
    assert resp.json['data'][0]['required_authentication_contexts'] == ['fedict']
529

  
530

  
531
def test_formdef_list_redirection(pub):
532
    FormDef.wipe()
533
    formdef = FormDef()
534
    formdef.name = 'test'
535
    formdef.disabled = True
536
    formdef.disabled_redirection = 'http://example.net'
537
    formdef.fields = []
538
    formdef.store()
539

  
540
    resp1 = get_app(pub).get(sign_uri('/json'))
541
    assert resp1.json['err'] == 0
542
    assert resp1.json['data'][0]['title'] == 'test'
543
    assert resp1.json['data'][0]['url'] == 'http://example.net/test/'
544
    assert resp1.json['data'][0]['redirection'] == True
545
    assert 'count' not in resp1.json['data'][0]
546

  
547

  
548
def test_backoffice_submission_formdef_list(pub, local_user):
549
    Role.wipe()
550
    role = Role(name='Foo bar')
551
    role.id = '14'
552
    role.store()
553

  
554
    FormDef.wipe()
555
    formdef = FormDef()
556
    formdef.name = 'test'
557
    formdef.description = 'plop'
558
    formdef.workflow_roles = {'_receiver': str(role.id)}
559
    formdef.fields = []
560
    formdef.store()
561

  
562
    formdef2 = FormDef()
563
    formdef2.name = 'ignore me'
564
    formdef2.fields = []
565
    formdef2.store()
566

  
567
    resp = get_app(pub).get(sign_uri('/api/formdefs/?backoffice-submission=on'))
568
    assert resp.json['err'] == 0
569
    assert len(resp.json['data']) == 0
570

  
571
    # check it's not advertised ...
572
    formdef.backoffice_submission_roles = [role.id]
573
    formdef.store()
574
    resp = get_app(pub).get(sign_uri('/api/formdefs/?backoffice-submission=on'))
575
    assert resp.json['err'] == 0
576
    assert len(resp.json['data']) == 0
577

  
578
    # even if it's advertised on frontoffice
579
    formdef.always_advertise = True
580
    formdef.store()
581
    resp = get_app(pub).get(sign_uri('/api/formdefs/?backoffice-submission=on'))
582
    assert resp.json['err'] == 0
583
    assert len(resp.json['data']) == 0
584

  
585
    # even if user is admin
586
    local_user.is_admin = True
587
    local_user.store()
588
    resp = get_app(pub).get(
589
        sign_uri('/api/formdefs/?backoffice-submission=on&NameID=%s' % local_user.name_identifiers[0])
590
    )
591
    assert resp.json['err'] == 0
592
    assert len(resp.json['data']) == 0
593
    local_user.is_admin = False
594
    local_user.store()
595

  
596
    # ... unless user has correct roles
597
    local_user.roles = [role.id]
598
    local_user.store()
599
    resp = get_app(pub).get(
600
        sign_uri('/api/formdefs/?backoffice-submission=on&NameID=%s' % local_user.name_identifiers[0])
601
    )
602
    assert resp.json['err'] == 0
603
    assert len(resp.json['data']) == 1
604
    assert 'backoffice_submission_url' in resp.json['data'][0]
605

  
606
    # but not advertised if it's a redirection
607
    formdef.disabled = True
608
    formdef.disabled_redirection = 'http://example.net'
609
    formdef.store()
610
    resp = get_app(pub).get(sign_uri('/api/formdefs/?backoffice-submission=on'))
611
    assert resp.json['err'] == 0
612
    assert len(resp.json['data']) == 0
613

  
614

  
615
def test_formdef_schema(pub):
616
    Workflow.wipe()
617
    workflow = Workflow(name='test')
618
    st1 = workflow.add_status('Status1', 'st1')
619
    jump = JumpWorkflowStatusItem()
620
    jump.status = 'st2'
621
    jump.timeout = 100
622
    st1.items.append(jump)
623
    st2 = workflow.add_status('Status2', 'st2')
624
    jump = JumpWorkflowStatusItem()
625
    jump.status = 'st3'
626
    st2.items.append(jump)
627
    st2 = workflow.add_status('Status3', 'st3')
628
    workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
629
    workflow.backoffice_fields_formdef.fields = [
630
        fields.StringField(id='bo1', label='1st backoffice field', type='string', varname='backoffice_blah'),
631
    ]
632
    workflow.store()
633
    FormDef.wipe()
634
    formdef = FormDef()
635
    formdef.name = 'test'
636
    formdef.fields = [
637
        fields.StringField(id='0', label='foobar'),
638
        fields.ItemField(
639
            id='1',
640
            label='foobar1',
641
            varname='foobar1',
642
            data_source={
643
                'type': 'json',
644
                'value': 'http://datasource.com',
645
            },
646
        ),
647
        fields.ItemsField(
648
            id='2',
649
            label='foobar2',
650
            varname='foobar2',
651
            data_source={
652
                'type': 'formula',
653
                'value': '[dict(id=i, text=\'label %s\' % i, foo=i) for i in range(10)]',
654
            },
655
        ),
656
    ]
657

  
658
    formdef.workflow_id = workflow.id
659
    formdef.store()
660

  
661
    with mock.patch('wcs.qommon.misc.urlopen') as urlopen:
662
        urlopen.side_effect = lambda *args: StringIO(
663
            '''\
664
{"data": [{"id": 0, "text": "zéro", "foo": "bar"}, \
665
{"id": 1, "text": "uné", "foo": "bar1"}, \
666
{"id": 2, "text": "deux", "foo": "bar2"}]}'''
667
        )
668
        resp = get_app(pub).get('/api/formdefs/test/schema')
669
        resp2 = get_app(pub).get('/test/schema')
670
        resp3 = get_app(pub).get(sign_url('/api/formdefs/test/schema?orig=coucou', '1234'))
671
    resp4 = get_app(pub).get(sign_url('/api/formdefs/test/schema?orig=coucou', '1234'))
672

  
673
    # check schema
674
    assert resp.json == resp2.json
675
    assert set(resp.json.keys()) >= set(
676
        [
677
            'enable_tracking_codes',
678
            'url_name',
679
            'description',
680
            'workflow',
681
            'expiration_date',
682
            'discussion',
683
            'has_captcha',
684
            'always_advertise',
685
            'name',
686
            'disabled',
687
            'only_allow_one',
688
            'fields',
689
            'keywords',
690
            'publication_date',
691
            'detailed_emails',
692
            'disabled_redirection',
693
        ]
694
    )
695
    assert resp.json['name'] == 'test'
696

  
697
    # fields checks
698
    assert resp.json['fields'][0]['label'] == 'foobar'
699
    assert resp.json['fields'][0]['type'] == 'string'
700

  
701
    assert resp.json['fields'][1]['label'] == 'foobar1'
702
    assert resp.json['fields'][1]['type'] == 'item'
703

  
704
    # check structured items are only exported for authenticated callers
705
    assert resp.json['fields'][1]['items'] == []
706
    assert resp.json['fields'][2]['items'] == []
707
    assert 'structured_items' not in resp.json['fields'][1]
708
    assert 'structured_items' not in resp.json['fields'][2]
709

  
710
    assert len(resp3.json['fields'][1]['structured_items']) == 3
711
    assert resp3.json['fields'][1]['structured_items'][0]['id'] == 0
712
    assert resp3.json['fields'][1]['structured_items'][0]['text'] == u'zéro'
713
    assert resp3.json['fields'][1]['structured_items'][0]['foo'] == 'bar'
714
    assert resp3.json['fields'][1]['items'][0] == u'zéro'
715

  
716
    assert resp3.json['fields'][2]['label'] == 'foobar2'
717
    assert resp3.json['fields'][2]['type'] == 'items'
718
    assert len(resp3.json['fields'][2]['structured_items']) == 10
719
    assert resp3.json['fields'][2]['structured_items'][0]['id'] == 0
720
    assert resp3.json['fields'][2]['structured_items'][0]['text'] == 'label 0'
721
    assert resp3.json['fields'][2]['structured_items'][0]['foo'] == 0
722
    assert resp3.json['fields'][2]['items'][0] == 'label 0'
723

  
724
    # if structured_items fails no values
725
    assert 'structured_items' not in resp4.json['fields'][1]
726
    assert resp4.json['fields'][1]['items'] == []
727

  
728
    # workflow checks
729
    assert len(resp.json['workflow']['statuses']) == 3
730
    assert resp.json['workflow']['statuses'][0]['id'] == 'st1'
731
    assert resp.json['workflow']['statuses'][0]['endpoint'] is False
732
    assert resp.json['workflow']['statuses'][0]['waitpoint'] is True
733
    assert resp.json['workflow']['statuses'][1]['id'] == 'st2'
734
    assert resp.json['workflow']['statuses'][1]['endpoint'] is False
735
    assert resp.json['workflow']['statuses'][1]['waitpoint'] is False
736
    assert resp.json['workflow']['statuses'][2]['id'] == 'st3'
737
    assert resp.json['workflow']['statuses'][2]['endpoint'] is True
738
    assert resp.json['workflow']['statuses'][2]['waitpoint'] is True
739
    assert len(resp.json['workflow']['fields']) == 1
740

  
741
    assert resp.json['workflow']['fields'][0]['label'] == '1st backoffice field'
742

  
743
    get_app(pub).get('/api/formdefs/xxx/schema', status=404)
744

  
745

  
746
def test_post_invalid_json(pub, local_user):
747
    resp = get_app(pub).post(
748
        '/api/formdefs/test/submit', params='not a json payload', content_type='application/json', status=400
749
    )
750
    assert resp.json['err'] == 1
751
    assert resp.json['err_class'] == 'Invalid request'
752

  
753

  
754
def test_formdef_submit(pub, local_user):
755
    Role.wipe()
756
    role = Role(name='test')
757
    role.store()
758
    local_user.roles = [role.id]
759
    local_user.store()
760

  
761
    FormDef.wipe()
762
    formdef = FormDef()
763
    formdef.name = 'test'
764
    formdef.fields = [fields.StringField(id='0', label='foobar')]
765
    formdef.store()
766
    data_class = formdef.data_class()
767

  
768
    resp = get_app(pub).post_json('/api/formdefs/test/submit', {'data': {}}, status=403)
769
    assert resp.json['err'] == 1
770
    assert resp.json['err_desc'] == 'unsigned API call'
771

  
772
    def url():
773
        signed_url = sign_url(
774
            'http://example.net/api/formdefs/test/submit'
775
            + '?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
776
            '1234',
777
        )
778
        return signed_url[len('http://example.net') :]
779

  
780
    resp = get_app(pub).post_json(url(), {'data': {}})
781
    assert resp.json['err'] == 0
782
    assert resp.json['data']['url'] == ('http://example.net/test/%s/' % resp.json['data']['id'])
783
    assert resp.json['data']['backoffice_url'] == (
784
        'http://example.net/backoffice/management/test/%s/' % resp.json['data']['id']
785
    )
786
    assert resp.json['data']['api_url'] == ('http://example.net/api/forms/test/%s/' % resp.json['data']['id'])
787
    assert data_class.get(resp.json['data']['id']).status == 'wf-new'
788
    assert data_class.get(resp.json['data']['id']).user_id == str(local_user.id)
789
    assert data_class.get(resp.json['data']['id']).tracking_code is None
790

  
791
    local_user2 = get_publisher().user_class()
792
    local_user2.name = 'Test'
793
    local_user2.email = 'foo@localhost'
794
    local_user2.store()
795
    resp = get_app(pub).post_json(url(), {'data': {}, 'user': {'NameID': [], 'email': local_user2.email}})
796
    assert data_class.get(resp.json['data']['id']).user.email == local_user2.email
797

  
798
    resp = get_app(pub).post(
799
        url(), json.dumps({'data': {}}), status=400
800
    )  # missing Content-Type: application/json header
801
    assert resp.json['err_desc'] == 'expected JSON but missing appropriate content-type'
802

  
803
    # check qualified content type are recognized
804
    resp = get_app(pub).post(url(), json.dumps({'data': {}}), content_type='application/json; charset=utf-8')
805
    assert resp.json['data']['url']
806

  
807
    formdef.disabled = True
808
    formdef.store()
809
    resp = get_app(pub).post_json(url(), {'data': {}}, status=403)
810
    assert resp.json['err'] == 1
811
    assert resp.json['err_desc'] == 'disabled form'
812

  
813
    formdef.disabled = False
814
    formdef.store()
815
    resp = get_app(pub).post_json(url(), {'meta': {'backoffice-submission': True}, 'data': {}}, status=403)
816
    formdef.backoffice_submission_roles = ['xx']
817
    formdef.store()
818
    resp = get_app(pub).post_json(url(), {'meta': {'backoffice-submission': True}, 'data': {}}, status=403)
819
    formdef.backoffice_submission_roles = [role.id]
820
    formdef.store()
821
    resp = get_app(pub).post_json(url(), {'meta': {'backoffice-submission': True}, 'data': {}})
822
    assert data_class.get(resp.json['data']['id']).status == 'wf-new'
823
    assert data_class.get(resp.json['data']['id']).backoffice_submission is True
824
    assert data_class.get(resp.json['data']['id']).user_id is None
825
    assert data_class.get(resp.json['data']['id']).submission_agent_id == str(local_user.id)
826

  
827
    formdef.enable_tracking_codes = True
828
    formdef.store()
829
    resp = get_app(pub).post_json(url(), {'data': {}})
830
    assert data_class.get(resp.json['data']['id']).tracking_code
831

  
832
    resp = get_app(pub).post_json(url(), {'meta': {'draft': True}, 'data': {}})
833
    assert data_class.get(resp.json['data']['id']).status == 'draft'
834

  
835
    resp = get_app(pub).post_json(
836
        url(),
837
        {
838
            'meta': {'backoffice-submission': True},
839
            'data': {},
840
            'context': {'channel': 'mail', 'comments': 'blah'},
841
        },
842
    )
843
    assert data_class.get(resp.json['data']['id']).status == 'wf-new'
844
    assert data_class.get(resp.json['data']['id']).backoffice_submission is True
845
    assert data_class.get(resp.json['data']['id']).user_id is None
846
    assert data_class.get(resp.json['data']['id']).submission_context == {'comments': 'blah'}
847
    assert data_class.get(resp.json['data']['id']).submission_channel == 'mail'
848

  
849
    data_class.wipe()
850

  
851

  
852
def test_formdef_submit_only_one(pub, local_user):
853
    Role.wipe()
854
    role = Role(name='test')
855
    role.store()
856
    local_user.roles = [role.id]
857
    local_user.store()
858

  
859
    FormDef.wipe()
860
    formdef = FormDef()
861
    formdef.name = 'test'
862
    formdef.only_allow_one = True
863
    formdef.fields = [fields.StringField(id='0', label='foobar')]
864
    formdef.store()
865
    data_class = formdef.data_class()
866

  
867
    def url():
868
        signed_url = sign_url(
869
            'http://example.net/api/formdefs/test/submit'
870
            + '?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
871
            '1234',
872
        )
873
        return signed_url[len('http://example.net') :]
874

  
875
    resp = get_app(pub).post_json(url(), {'data': {}})
876
    assert data_class.get(resp.json['data']['id']).user_id == str(local_user.id)
877

  
878
    assert data_class.count() == 1
879

  
880
    resp = get_app(pub).post_json(url(), {'data': {}}, status=403)
881
    assert resp.json['err'] == 1
882
    assert resp.json['err_desc'] == 'only one formdata by user is allowed'
883

  
884
    formdata = data_class.select()[0]
885
    formdata.user_id = '1000'  # change owner
886
    formdata.store()
887

  
888
    resp = get_app(pub).post_json(url(), {'data': {}}, status=200)
889
    assert data_class.get(resp.json['data']['id']).user_id == str(local_user.id)
890
    assert data_class.count() == 2
891

  
892

  
893
def test_formdef_submit_with_varname(pub, local_user):
894
    NamedDataSource.wipe()
895
    data_source = NamedDataSource(name='foobar')
896
    source = [{'id': '1', 'text': 'foo', 'more': 'XXX'}, {'id': '2', 'text': 'bar', 'more': 'YYY'}]
897
    data_source.data_source = {'type': 'formula', 'value': repr(source)}
898
    data_source.store()
899

  
900
    data_source = NamedDataSource(name='foobar_jsonp')
901
    data_source.data_source = {'type': 'formula', 'value': 'http://example.com/jsonp'}
902
    data_source.store()
903

  
904
    Role.wipe()
905
    role = Role(name='test')
906
    role.store()
907
    local_user.roles = [role.id]
908
    local_user.store()
909

  
910
    FormDef.wipe()
911
    formdef = FormDef()
912
    formdef.name = 'test'
913
    formdef.fields = [
914
        fields.StringField(id='0', label='foobar0', varname='foobar0'),
915
        fields.ItemField(id='1', label='foobar1', varname='foobar1', data_source={'type': 'foobar'}),
916
        fields.ItemField(id='2', label='foobar2', varname='foobar2', data_source={'type': 'foobar_jsonp'}),
917
        fields.DateField(id='3', label='foobar3', varname='date'),
918
        fields.FileField(id='4', label='foobar4', varname='file'),
919
        fields.MapField(id='5', label='foobar5', varname='map'),
920
        fields.StringField(id='6', label='foobar6', varname='foobar6'),
921
    ]
922
    formdef.store()
923
    data_class = formdef.data_class()
924

  
925
    resp = get_app(pub).post_json('/api/formdefs/test/submit', {'data': {}}, status=403)
926
    assert resp.json['err'] == 1
927
    assert resp.json['err_desc'] == 'unsigned API call'
928

  
929
    signed_url = sign_url(
930
        'http://example.net/api/formdefs/test/submit'
931
        + '?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
932
        '1234',
933
    )
934
    url = signed_url[len('http://example.net') :]
935
    payload = {
936
        'data': {
937
            'foobar0': 'xxx',
938
            'foobar1': '1',
939
            'foobar1_structured': {
940
                'id': '1',
941
                'text': 'foo',
942
                'more': 'XXX',
943
            },
944
            'foobar2': 'bar',
945
            'foobar2_raw': '10',
946
            'date': '1970-01-01',
947
            'file': {
948
                'filename': 'test.txt',
949
                'content': force_text(base64.b64encode(b'test')),
950
            },
951
            'map': {
952
                'lat': 1.5,
953
                'lon': 2.25,
954
            },
955
        }
956
    }
957
    resp = get_app(pub).post_json(url, payload)
958
    assert resp.json['err'] == 0
959
    assert data_class.get(resp.json['data']['id']).status == 'wf-new'
960
    assert data_class.get(resp.json['data']['id']).user_id == str(local_user.id)
961
    assert data_class.get(resp.json['data']['id']).tracking_code is None
962
    assert data_class.get(resp.json['data']['id']).data['0'] == 'xxx'
963
    assert data_class.get(resp.json['data']['id']).data['1'] == '1'
964
    assert data_class.get(resp.json['data']['id']).data['1_structured'] == source[0]
965
    assert data_class.get(resp.json['data']['id']).data['2'] == '10'
966
    assert data_class.get(resp.json['data']['id']).data['2_display'] == 'bar'
967
    assert data_class.get(resp.json['data']['id']).data['3'] == time.struct_time(
968
        (1970, 1, 1, 0, 0, 0, 3, 1, -1)
969
    )
970

  
971
    assert data_class.get(resp.json['data']['id']).data['4'].orig_filename == 'test.txt'
972
    assert data_class.get(resp.json['data']['id']).data['4'].get_content() == b'test'
973
    assert data_class.get(resp.json['data']['id']).data['5'] == '1.5;2.25'
974
    # test bijectivity
975
    assert (
976
        formdef.fields[3].get_json_value(data_class.get(resp.json['data']['id']).data['3'])
977
        == payload['data']['date']
978
    )
979
    for k in payload['data']['file']:
980
        data = data_class.get(resp.json['data']['id']).data['4']
981
        assert formdef.fields[4].get_json_value(data)[k] == payload['data']['file'][k]
982
    assert (
983
        formdef.fields[5].get_json_value(data_class.get(resp.json['data']['id']).data['5'])
984
        == payload['data']['map']
985
    )
986

  
987
    data_class.wipe()
988

  
989

  
990
def test_formdef_submit_from_wscall(pub, local_user):
991
    test_formdef_submit_with_varname(pub, local_user)
992
    formdef = FormDef.select()[0]
993
    workflow = Workflow.get_default_workflow()
994
    workflow.id = '2'
995
    workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
996
    workflow.backoffice_fields_formdef.fields = [
997
        fields.StringField(id='bo1', label='1st backoffice field', type='string', varname='backoffice_blah'),
998
    ]
999
    workflow.store()
1000
    formdef.workflow = workflow
1001
    formdef.store()
1002

  
1003
    formdata = formdef.data_class()()
1004
    formdata.just_created()
1005

  
1006
    upload = PicklableUpload('test.txt', 'text/plain', 'ascii')
1007
    upload.receive([b'test'])
1008

  
1009
    formdata.data = {
1010
        '0': 'xxx',
1011
        '1': '1',
1012
        '1_display': '1',
1013
        '1_structured': {
1014
            'id': '1',
1015
            'text': 'foo',
1016
            'more': 'XXX',
1017
        },
1018
        '2': '10',
1019
        '2_display': 'bar',
1020
        '3': time.strptime('1970-01-01', '%Y-%m-%d'),
1021
        '4': upload,
1022
        '5': '1.5;2.25',
1023
        'bo1': 'backoffice field',
1024
    }
1025
    formdata.just_created()
1026
    formdata.evolution[-1].status = 'wf-new'
1027
    formdata.store()
1028

  
1029
    payload = json.loads(json.dumps(formdata.get_json_export_dict(), cls=qommon.misc.JSONEncoder))
1030
    signed_url = sign_url('http://example.net/api/formdefs/test/submit?orig=coucou', '1234')
1031
    url = signed_url[len('http://example.net') :]
1032

  
1033
    resp = get_app(pub).post_json(url, payload)
1034
    assert resp.json['err'] == 0
1035
    new_formdata = formdef.data_class().get(resp.json['data']['id'])
1036
    assert new_formdata.data['0'] == formdata.data['0']
1037
    assert new_formdata.data['1'] == formdata.data['1']
1038
    assert new_formdata.data['1_display'] == formdata.data['1_display']
1039
    assert new_formdata.data['1_structured'] == formdata.data['1_structured']
1040
    assert new_formdata.data['2'] == formdata.data['2']
1041
    assert new_formdata.data['2_display'] == formdata.data['2_display']
1042
    assert new_formdata.data['3'] == formdata.data['3']
1043
    assert new_formdata.data['4'].get_content() == formdata.data['4'].get_content()
1044
    assert new_formdata.data['5'] == formdata.data['5']
1045
    assert new_formdata.data['bo1'] == formdata.data['bo1']
1046
    assert not new_formdata.data.get('6')
1047
    assert new_formdata.user_id is None
1048

  
1049
    # add an extra attribute
1050
    payload['extra'] = {'foobar6': 'YYY'}
1051
    signed_url = sign_url('http://example.net/api/formdefs/test/submit?orig=coucou', '1234')
1052
    url = signed_url[len('http://example.net') :]
1053
    resp = get_app(pub).post_json(url, payload)
1054
    assert resp.json['err'] == 0
1055
    new_formdata = formdef.data_class().get(resp.json['data']['id'])
1056
    assert new_formdata.data['0'] == formdata.data['0']
1057
    assert new_formdata.data['6'] == 'YYY'
1058

  
1059
    # add user
1060
    formdata.user_id = local_user.id
1061
    formdata.store()
1062

  
1063
    payload = json.loads(json.dumps(formdata.get_json_export_dict(), cls=qommon.misc.JSONEncoder))
1064
    signed_url = sign_url('http://example.net/api/formdefs/test/submit?orig=coucou', '1234')
1065
    url = signed_url[len('http://example.net') :]
1066

  
1067
    resp = get_app(pub).post_json(url, payload)
1068
    assert resp.json['err'] == 0
1069
    new_formdata = formdef.data_class().get(resp.json['data']['id'])
1070
    assert str(new_formdata.user_id) == str(local_user.id)
1071

  
1072
    # test missing map data
1073
    del formdata.data['5']
1074

  
1075
    payload = json.loads(json.dumps(formdata.get_json_export_dict(), cls=qommon.misc.JSONEncoder))
1076
    signed_url = sign_url('http://example.net/api/formdefs/test/submit?orig=coucou', '1234')
1077
    url = signed_url[len('http://example.net') :]
1078

  
1079
    resp = get_app(pub).post_json(url, payload)
1080
    assert resp.json['err'] == 0
1081
    new_formdata = formdef.data_class().get(resp.json['data']['id'])
1082
    assert new_formdata.data.get('5') is None
1083

  
1084

  
1085
def test_categories(pub):
1086
    FormDef.wipe()
1087
    Category.wipe()
1088
    category = Category()
1089
    category.name = 'Category'
1090
    category.description = 'hello world'
1091
    category.store()
1092

  
1093
    resp = get_app(pub).get('/api/categories/', headers={'Accept': 'application/json'})
1094
    assert resp.json['data'] == []  # no advertised forms
1095

  
1096
    formdef = FormDef()
1097
    formdef.name = 'test'
1098
    formdef.category_id = category.id
1099
    formdef.fields = []
1100
    formdef.keywords = 'mobile, test'
1101
    formdef.store()
1102
    formdef.data_class().wipe()
1103

  
1104
    formdef = FormDef()
1105
    formdef.name = 'test 2'
1106
    formdef.category_id = category.id
1107
    formdef.fields = []
1108
    formdef.keywords = 'foobar'
1109
    formdef.store()
1110
    formdef.data_class().wipe()
1111

  
1112
    resp = get_app(pub).get('/api/categories/')
1113
    resp2 = get_app(pub).get('/categories', headers={'Accept': 'application/json'})
1114
    assert resp.json == resp2.json
1115
    assert resp.json['data'][0]['title'] == 'Category'
1116
    assert resp.json['data'][0]['url'] == 'http://example.net/category/'
1117
    assert resp.json['data'][0]['description'] == '<p>hello world</p>'
1118
    assert set(resp.json['data'][0]['keywords']) == set(['foobar', 'mobile', 'test'])
1119
    assert not 'forms' in resp.json['data'][0]
1120

  
1121
    # check HTML description
1122
    category.description = '<p><strong>hello world</strong></p>'
1123
    category.store()
1124
    resp = get_app(pub).get('/api/categories/')
1125
    assert resp.json['data'][0]['description'] == category.description
1126

  
1127

  
1128
def test_categories_private(pub, local_user):
1129
    FormDef.wipe()
1130
    Category.wipe()
1131
    category = Category()
1132
    category.name = 'Category'
1133
    category.description = 'hello world'
1134
    category.store()
1135

  
1136
    formdef = FormDef()
1137
    formdef.name = 'test'
1138
    formdef.category_id = category.id
1139
    formdef.fields = []
1140
    formdef.store()
1141
    formdef.data_class().wipe()
1142

  
1143
    # open form
1144
    resp = get_app(pub).get('/api/categories/')
1145
    assert len(resp.json['data']) == 1
1146

  
1147
    # private form, the category doesn't appear anymore
1148
    formdef.roles = ['plop']
1149
    formdef.store()
1150
    resp = get_app(pub).get('/api/categories/')
1151
    assert len(resp.json['data']) == 0
1152

  
1153
    # not even for a signed request specifying an user
1154
    resp = get_app(pub).get(sign_uri('http://example.net/api/categories/', local_user))
1155
    assert len(resp.json['data']) == 0
1156

  
1157
    # but it appears if this is a signed request without user
1158
    resp = get_app(pub).get(sign_uri('http://example.net/api/categories/'))
1159
    assert len(resp.json['data']) == 1
1160

  
1161
    # or signed with an authorised user
1162
    local_user.roles = ['plop']
1163
    local_user.store()
1164
    resp = get_app(pub).get(sign_uri('http://example.net/api/categories/', local_user))
1165
    assert len(resp.json['data']) == 1
1166

  
1167

  
1168
def test_categories_formdefs(pub, local_user):
1169
    FormDef.wipe()
1170
    Category.wipe()
1171
    category = Category()
1172
    category.name = 'Category'
1173
    category.description = 'hello world'
1174
    category.store()
1175

  
1176
    formdef = FormDef()
1177
    formdef.name = 'test'
1178
    formdef.category_id = category.id
1179
    formdef.fields = []
1180
    formdef.keywords = 'mobile, test'
1181
    formdef.store()
1182
    formdef.data_class().wipe()
1183

  
1184
    formdef = FormDef()
1185
    formdef.name = 'test 2'
1186
    formdef.category_id = category.id
1187
    formdef.fields = []
1188
    formdef.keywords = 'foobar'
1189
    formdef.store()
1190
    formdef.data_class().wipe()
1191

  
1192
    formdef2 = FormDef()
1193
    formdef2.name = 'other test'
1194
    formdef2.category_id = None
1195
    formdef2.fields = []
1196
    formdef2.store()
1197
    formdef2.data_class().wipe()
1198

  
1199
    formdef2 = FormDef()
1200
    formdef2.name = 'test disabled'
1201
    formdef2.category_id = category.id
1202
    formdef2.fields = []
1203
    formdef2.disabled = True
1204
    formdef2.store()
1205
    formdef2.data_class().wipe()
1206

  
1207
    resp = get_app(pub).get('/api/categories/category/formdefs/', status=403)
1208
    resp2 = get_app(pub).get('/category/json', status=403)
1209
    resp = get_app(pub).get(sign_uri('/api/categories/category/formdefs/'))
1210
    resp2 = get_app(pub).get(sign_uri('/category/json'))
1211
    assert resp.json == resp2.json
1212
    assert resp.json['err'] == 0
1213
    assert len(resp.json['data']) == 2
1214
    assert resp.json['data'][0]['title'] == 'test'
1215
    assert resp.json['data'][0]['url'] == 'http://example.net/test/'
1216
    assert resp.json['data'][0]['redirection'] == False
1217
    assert resp.json['data'][0]['category'] == 'Category'
1218
    assert resp.json['data'][0]['category_slug'] == 'category'
1219
    assert 'count' not in resp.json['data'][0]
1220

  
1221
    resp = get_app(pub).get(sign_uri('/api/categories/category/formdefs/?include-count=on'))
1222
    assert resp.json['data'][0]['title'] == 'test'
1223
    assert resp.json['data'][0]['url'] == 'http://example.net/test/'
1224
    assert resp.json['data'][0]['count'] == 0
1225

  
1226
    resp = get_app(pub).get(sign_uri('/api/categories/category/formdefs/?include-disabled=on'))
1227
    assert len(resp.json['data']) == 3
1228
    assert resp.json['data'][2]['title'] == 'test disabled'
1229

  
1230
    get_app(pub).get('/api/categories/XXX/formdefs/', status=404)
1231

  
1232
    resp = get_app(pub).get(sign_uri('/api/categories/category/formdefs/?backoffice-submission=on'))
1233
    assert resp.json['err'] == 0
1234
    assert len(resp.json['data']) == 0
1235

  
1236
    Role.wipe()
1237
    role = Role(name='test')
1238
    role.store()
1239
    local_user.roles = []
1240
    local_user.store()
1241
    # check it's not advertised ...
1242
    formdef.backoffice_submission_roles = [role.id]
1243
    formdef.store()
1244
    resp = get_app(pub).get(sign_uri('/api/categories/category/formdefs/?backoffice-submission=on'))
1245
    assert resp.json['err'] == 0
1246
    assert len(resp.json['data']) == 0
1247
    resp = get_app(pub).get(
1248
        sign_uri(
1249
            '/api/categories/category/formdefs/?backoffice-submission=on&NameID=%s'
1250
            % local_user.name_identifiers[0]
1251
        )
1252
    )
1253
    assert resp.json['err'] == 0
1254
    assert len(resp.json['data']) == 0
1255
    # ... unless user has correct roles
1256
    local_user.roles = [role.id]
1257
    local_user.store()
1258
    resp = get_app(pub).get(
1259
        sign_uri(
1260
            '/api/categories/category/formdefs/?backoffice-submission=on&NameID=%s'
1261
            % local_user.name_identifiers[0]
1262
        )
1263
    )
1264
    assert resp.json['err'] == 0
1265
    assert len(resp.json['data']) == 1
1266

  
1267

  
1268
def test_categories_full(pub):
1269
    test_categories(pub)
1270
    resp = get_app(pub).get('/api/categories/?full=on')
1271
    assert len(resp.json['data'][0]['forms']) == 2
1272
    assert resp.json['data'][0]['forms'][0]['title'] == 'test'
1273
    assert resp.json['data'][0]['forms'][1]['title'] == 'test 2'
1274

  
1275

  
1276
def test_formdata(pub, local_user):
1277
    NamedDataSource.wipe()
1278
    data_source = NamedDataSource(name='foobar')
1279
    data_source.data_source = {
1280
        'type': 'formula',
1281
        'value': repr([{'id': '1', 'text': 'foo', 'more': 'XXX'}, {'id': '2', 'text': 'bar', 'more': 'YYY'}]),
1282
    }
1283
    data_source.store()
1284

  
1285
    BlockDef.wipe()
1286
    block = BlockDef()
1287
    block.name = 'foobar'
1288
    block.fields = [
1289
        fields.StringField(id='abc', label='Foo', varname='foo'),
1290
        fields.ItemField(id='xyz', label='Test', type='item', data_source={'type': 'foobar'}, varname='bar'),
1291
    ]
1292
    block.store()
1293

  
1294
    Role.wipe()
1295
    role = Role(name='test')
1296
    role.id = '123'
1297
    role.store()
1298
    another_role = Role(name='another')
1299
    another_role.id = '321'
1300
    another_role.store()
1301
    FormDef.wipe()
1302
    formdef = FormDef()
1303
    formdef.geolocations = {'base': 'blah'}
1304
    formdef.name = 'test'
1305
    formdef.fields = [
1306
        fields.StringField(id='0', label='foobar', varname='foobar'),
1307
        fields.StringField(id='1', label='foobar2'),
1308
        fields.DateField(id='2', label='foobar3', varname='date'),
1309
        fields.FileField(id='3', label='foobar4', varname='file'),
1310
        fields.ItemField(id='4', label='foobar5', varname='item', data_source={'type': 'foobar'}),
1311
        fields.BlockField(id='5', label='test', varname='blockdata', type='block:foobar', max_items=3),
1312
    ]
1313
    Workflow.wipe()
1314
    workflow = Workflow(name='foo')
1315
    workflow.possible_status = Workflow.get_default_workflow().possible_status[:]
1316
    workflow.roles['_foobar'] = 'Foobar'
1317
    workflow.store()
1318
    formdef.workflow_id = workflow.id
1319
    formdef.workflow_roles = {'_receiver': role.id, '_foobar': another_role.id}
1320
    formdef.store()
1321
    item_field = formdef.fields[4]
1322

  
1323
    formdef.data_class().wipe()
1324
    formdata = formdef.data_class()()
1325
    date = time.strptime('2014-01-20', '%Y-%m-%d')
1326
    upload = PicklableUpload('test.txt', 'text/plain', 'ascii')
1327
    upload.receive([b'base64me'])
1328
    formdata.data = {
1329
        '0': 'foo@localhost',
1330
        '1': 'xxx',
1331
        '2': date,
1332
        '3': upload,
1333
        '4': '1',
1334
        '5': {
1335
            'data': [
1336
                {'abc': 'plop', 'xyz': '1', 'xyz_display': 'foo', 'xyz_structured': 'XXX'},
1337
            ],
1338
            'schema': {},  # not important here
1339
        },
1340
        '5_display': 'hello',
1341
    }
1342
    formdata.data['4_display'] = item_field.store_display_value(formdata.data, item_field.id)
1343
    formdata.data['4_structured'] = item_field.store_structured_value(formdata.data, item_field.id)
1344
    formdata.user_id = local_user.id
1345
    formdata.just_created()
1346
    formdata.status = 'wf-new'
1347
    formdata.evolution[-1].status = 'wf-new'
1348
    formdata.geolocations = {'base': {'lon': 10, 'lat': -12}}
1349
    formdata.store()
1350

  
1351
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user), status=403)
1352

  
1353
    local_user.roles = [role.id]
1354
    local_user.store()
1355
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user), status=200)
1356

  
1357
    assert datetime.datetime.strptime(resp.json['last_update_time'], '%Y-%m-%dT%H:%M:%S')
1358
    assert datetime.datetime.strptime(resp.json['receipt_time'], '%Y-%m-%dT%H:%M:%S')
1359
    assert len(resp.json['fields']) == 8
1360
    assert 'foobar' in resp.json['fields']
1361
    assert 'foobar2' not in resp.json['fields']  # foobar2 has no varname, not in json
1362
    assert resp.json['user']['name'] == local_user.name
1363
    assert resp.json['fields']['foobar'] == 'foo@localhost'
1364
    assert resp.json['fields']['date'] == '2014-01-20'
1365
    assert resp.json['fields']['file']['content'] == 'YmFzZTY0bWU='  # base64('base64me')
1366
    assert resp.json['fields']['file']['filename'] == 'test.txt'
1367
    assert resp.json['fields']['file']['content_type'] == 'text/plain'
1368
    assert resp.json['fields']['item'] == 'foo'
1369
    assert resp.json['fields']['item_raw'] == '1'
1370
    assert resp.json['fields']['item_structured'] == {'id': '1', 'text': 'foo', 'more': 'XXX'}
1371
    assert resp.json['fields']['blockdata'] == 'hello'
1372
    assert resp.json['fields']['blockdata_raw'] == [
1373
        {'foo': 'plop', 'bar': 'foo', 'bar_raw': '1', 'bar_structured': 'XXX'}
1374
    ]
1375
    assert resp.json['workflow']['status']['name'] == 'New'
1376
    assert resp.json['workflow']['real_status']['name'] == 'New'
1377
    assert resp.json['submission']['channel'] == 'web'
1378
    assert resp.json['geolocations']['base']['lon'] == 10
1379
    assert resp.json['geolocations']['base']['lat'] == -12
1380

  
1381
    assert [x.get('id') for x in resp.json['roles']['_receiver']] == [str(role.id)]
1382
    assert [x.get('id') for x in resp.json['roles']['_foobar']] == [str(another_role.id)]
1383
    assert set([x.get('id') for x in resp.json['roles']['concerned']]) == set(
1384
        [str(role.id), str(another_role.id)]
1385
    )
1386
    assert [x.get('id') for x in resp.json['roles']['actions']] == [str(role.id)]
1387

  
1388
    # check the ?format=json endpoint returns 403
1389
    resp = get_app(pub).get('/test/%s/?format=json' % formdata.id, status=403)
1390
    resp2 = get_app(pub).get(sign_uri('/test/%s/' % formdata.id, user=local_user), status=403)
1391

  
1392
    # check status visibility
1393
    workflow.add_status('Status1', 'st1')
1394
    workflow.possible_status[-1].visibility = ['unknown']
1395
    workflow.store()
1396
    formdata.jump_status('st1')
1397
    assert formdata.status == 'wf-st1'
1398

  
1399
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user), status=200)
1400
    assert resp.json['workflow']['status'] == {'id': 'new', 'name': 'New'}
1401
    assert resp.json['workflow']['real_status'] == {'id': 'st1', 'name': 'Status1'}
1402

  
1403

  
1404
def test_formdata_backoffice_fields(pub, local_user):
1405
    test_formdata(pub, local_user)
1406
    Workflow.wipe()
1407
    workflow = Workflow(name='foo')
1408
    workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
1409
    workflow.backoffice_fields_formdef.fields = [
1410
        fields.StringField(id='bo1', label='1st backoffice field', type='string', varname='backoffice_blah'),
1411
    ]
1412
    workflow.store()
1413

  
1414
    formdef = FormDef.select()[0]
1415
    formdata = formdef.data_class().select()[0]
1416
    formdata.data['bo1'] = 'Hello world'
1417
    formdata.store()
1418

  
1419
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user))
1420
    assert resp.json['workflow']['fields']['backoffice_blah'] == 'Hello world'
1421

  
1422

  
1423
def test_formdata_duplicated_varnames(pub, local_user):
1424
    Role.wipe()
1425
    role = Role(name='test')
1426
    role.id = '123'
1427
    role.store()
1428
    another_role = Role(name='another')
1429
    another_role.id = '321'
1430
    another_role.store()
1431
    FormDef.wipe()
1432
    formdef = FormDef()
1433
    formdef.geolocations = {'base': 'blah'}
1434
    formdef.name = 'test'
1435
    formdef.fields = [
1436
        fields.StringField(id='0', label='foobar', varname='foobar'),
1437
        fields.StringField(id='1', label='foobar2', varname='foobar'),
1438
    ]
1439
    workflow = Workflow.get_default_workflow()
1440
    workflow.roles['_foobar'] = 'Foobar'
1441
    workflow.id = '2'
1442
    workflow.store()
1443
    formdef.workflow_id = workflow.id
1444
    formdef.workflow_roles = {'_receiver': role.id, '_foobar': another_role.id}
1445
    formdef.store()
1446

  
1447
    formdef.data_class().wipe()
1448
    formdata = formdef.data_class()()
1449
    formdata.data = {
1450
        '0': 'foo',
1451
        '1': 'bar',
1452
    }
1453
    formdata.user_id = local_user.id
1454
    formdata.just_created()
1455
    formdata.status = 'wf-new'
1456
    formdata.evolution[-1].status = 'wf-new'
1457
    formdata.store()
1458

  
1459
    local_user.roles = [role.id]
1460
    local_user.store()
1461
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user), status=200)
1462
    assert resp.json['fields'] == {'foobar': 'foo'}
1463

  
1464
    formdata.data = {
1465
        '0': 'foo',
1466
        '1': '',
1467
    }
1468
    formdata.store()
1469
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user), status=200)
1470
    assert resp.json['fields'] == {'foobar': 'foo'}
1471

  
1472
    formdata.data = {
1473
        '0': '',
1474
        '1': 'foo',
1475
    }
1476
    formdata.store()
1477
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user), status=200)
1478
    assert resp.json['fields'] == {'foobar': 'foo'}
1479

  
1480

  
1481
def test_formdata_edit(pub, local_user):
1482
    Role.wipe()
1483
    role = Role(name='test')
1484
    role.id = '123'
1485
    role.store()
1486
    another_role = Role(name='another')
1487
    another_role.id = '321'
1488
    another_role.store()
1489
    local_user.roles = [role.id]
1490
    local_user.store()
1491
    FormDef.wipe()
1492
    formdef = FormDef()
1493
    formdef.name = 'test'
1494
    formdef.fields = [
1495
        fields.StringField(id='0', label='foobar', varname='foobar'),
1496
    ]
1497
    Workflow.wipe()
1498
    workflow = Workflow(name='foo')
1499
    workflow.possible_status = Workflow.get_default_workflow().possible_status[:]
1500
    workflow.roles['_foobar'] = 'Foobar'
1501
    workflow.store()
1502
    formdef.workflow_id = workflow.id
1503
    formdef.workflow_roles = {'_receiver': role.id, '_foobar': another_role.id}
1504
    formdef.store()
1505
    formdef.data_class().wipe()
1506
    formdata = formdef.data_class()()
1507
    formdata.data = {
1508
        '0': 'foo@localhost',
1509
    }
1510
    formdata.user_id = local_user.id
1511
    formdata.just_created()
1512
    formdata.status = 'wf-new'
1513
    formdata.evolution[-1].status = 'wf-new'
1514
    formdata.store()
1515

  
1516
    # not user
1517
    get_app(pub).post_json(
1518
        sign_uri('/api/forms/test/%s/' % formdata.id), {'data': {'0': 'bar@localhost'}}, status=403
1519
    )
1520

  
1521
    # no editable action
1522
    get_app(pub).post_json(
1523
        sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user),
1524
        {'data': {'0': 'bar@localhost'}},
1525
        status=403,
1526
    )
1527

  
1528
    wfedit = EditableWorkflowStatusItem()
1529
    wfedit.id = '_wfedit'
1530
    wfedit.by = [local_user.roles[0]]
1531
    workflow.possible_status[1].items.append(wfedit)
1532
    wfedit.parent = workflow.possible_status[1]
1533
    workflow.store()
1534

  
1535
    get_app(pub).post_json(
1536
        sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user),
1537
        {'data': {'0': 'bar@localhost'}},
1538
        status=200,
1539
    )
1540
    assert formdef.data_class().select()[0].data['0'] == 'bar@localhost'
1541

  
1542
    # not editable by user role
1543
    wfedit.by = ['XX']
1544
    workflow.store()
1545
    get_app(pub).post_json(
1546
        sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user),
1547
        {'data': {'0': 'bar@localhost'}},
1548
        status=403,
1549
    )
1550

  
1551
    # edit + jump
1552
    wfedit.status = 'rejected'
1553
    wfedit.by = [local_user.roles[0]]
1554
    workflow.store()
1555

  
1556
    get_app(pub).post_json(
1557
        sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user),
1558
        {'data': {'0': 'bar2@localhost'}},
1559
        status=200,
1560
    )
1561
    assert formdef.data_class().select()[0].data['0'] == 'bar2@localhost'
1562
    assert formdef.data_class().select()[0].status == 'wf-rejected'
1563

  
1564

  
1565
def test_formdata_with_workflow_data(pub, local_user):
1566
    Role.wipe()
1567
    role = Role(name='test')
1568
    role.id = '123'
1569
    role.store()
1570

  
1571
    local_user.roles = [role.id]
1572
    local_user.store()
1573

  
1574
    FormDef.wipe()
1575
    formdef = FormDef()
1576
    formdef.name = 'test'
1577
    formdef.fields = []
1578
    workflow = Workflow.get_default_workflow()
1579
    workflow.id = '2'
1580
    workflow.store()
1581
    formdef.workflow_id = workflow.id
1582
    formdef.workflow_roles = {'_receiver': role.id}
1583
    formdef.store()
1584

  
1585
    formdef.data_class().wipe()
1586
    formdata = formdef.data_class()()
1587
    formdata.just_created()
1588
    formdata.status = 'wf-new'
1589
    formdata.evolution[-1].status = 'wf-new'
1590

  
1591
    from wcs.qommon.form import PicklableUpload as PicklableUpload3
1592

  
1593
    upload = PicklableUpload3('test.txt', 'text/plain', 'ascii')
1594
    upload.receive([b'test'])
1595
    upload2 = PicklableUpload3('test.txt', 'text/plain', 'ascii')
1596
    upload2.receive([b'test'])
1597
    formdata.workflow_data = {'blah': upload, 'blah2': upload2, 'xxx': 23}
1598
    formdata.store()
1599

  
1600
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user))
1601
    assert resp.json['workflow']['data']['xxx'] == 23
1602
    assert resp.json['workflow']['data']['blah']['filename'] == 'test.txt'
1603
    assert resp.json['workflow']['data']['blah']['content_type'] == 'text/plain'
1604
    assert base64.decodebytes(force_bytes(resp.json['workflow']['data']['blah']['content'])) == b'test'
1605
    assert base64.decodebytes(force_bytes(resp.json['workflow']['data']['blah2']['content'])) == b'test'
1606

  
1607

  
1608
def test_user_by_nameid(pub, local_user):
1609
    resp = get_app(pub).get(sign_uri('/api/users/xyz/', user=local_user), status=404)
1610
    local_user.name_identifiers = ['xyz']
1611
    local_user.store()
1612
    resp = get_app(pub).get(sign_uri('/api/users/xyz/', user=local_user))
1613
    assert str(resp.json['id']) == str(local_user.id)
1614

  
1615

  
1616
def test_user_roles(pub, local_user):
1617
    local_user.name_identifiers = ['xyz']
1618
    local_user.store()
1619
    role = Role(name='Foo bar')
1620
    role.store()
1621
    local_user.roles = [role.id]
1622
    local_user.store()
1623
    resp = get_app(pub).get(sign_uri('/api/users/xyz/', user=local_user))
1624
    assert len(resp.json['user_roles']) == 1
1625
    assert resp.json['user_roles'][0]['name'] == 'Foo bar'
1626

  
1627

  
1628
def test_user_forms(pub, local_user):
1629
    Workflow.wipe()
1630
    workflow = Workflow.get_default_workflow()
1631
    workflow.id = '2'
1632
    workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow)
1633
    workflow.variables_formdef.fields.append(
1634
        fields.DateField(label='Test', type='date', varname='option_date')
1635
    )
1636
    workflow.store()
1637

  
1638
    FormDef.wipe()
1639
    formdef = FormDef()
1640
    formdef.name = 'test'
1641
    formdef.fields = [
1642
        fields.StringField(id='0', label='foobar', varname='foobar'),
1643
        fields.StringField(id='1', label='foobar2'),
1644
        fields.DateField(id='2', label='date', type='date', varname='date'),
1645
    ]
1646
    formdef.keywords = 'hello, world'
1647
    formdef.disabled = False
1648
    formdef.enable_tracking_codes = True
1649
    formdef.workflow = workflow
1650
    formdef.workflow_options = {'option_date': datetime.date(2020, 1, 15).timetuple()}
1651
    formdef.store()
1652
    formdef.data_class().wipe()
1653

  
1654
    resp = get_app(pub).get(sign_uri('/api/user/forms', user=local_user))
1655
    assert resp.json['err'] == 0
1656
    assert len(resp.json['data']) == 0
1657

  
1658
    formdata = formdef.data_class()()
1659
    formdata.data = {
1660
        '0': 'foo@localhost',
1661
        '1': 'xxx',
1662
        '2': datetime.date(2020, 1, 15).timetuple(),
1663
    }
1664
    formdata.user_id = local_user.id
1665
    formdata.just_created()
1666
    formdata.jump_status('new')
1667
    formdata.store()
1668

  
1669
    resp = get_app(pub).get(sign_uri('/api/user/forms', user=local_user))
1670
    resp2 = get_app(pub).get(sign_uri('/myspace/forms', user=local_user))
1671
    resp3 = get_app(pub).get(sign_uri('/api/users/%s/forms' % local_user.id))
1672
    assert resp.json['err'] == 0
1673
    assert len(resp.json['data']) == 1
1674
    assert resp.json['data'][0]['form_name'] == 'test'
1675
    assert resp.json['data'][0]['form_slug'] == 'test'
1676
    assert resp.json['data'][0]['form_status'] == 'New'
1677
    assert datetime.datetime.strptime(resp.json['data'][0]['form_receipt_datetime'], '%Y-%m-%dT%H:%M:%S')
1678
    assert resp.json['data'][0]['keywords'] == ['hello', 'world']
1679
    assert resp.json == resp2.json == resp3.json
1680

  
1681
    resp = get_app(pub).get(sign_uri('/api/user/forms?full=on', user=local_user))
1682
    assert resp.json['err'] == 0
1683
    assert resp.json['data'][0]['fields']['foobar'] == 'foo@localhost'
1684
    assert resp.json['data'][0]['fields']['date'] == '2020-01-15'
1685
    assert resp.json['data'][0]['keywords'] == ['hello', 'world']
1686
    assert resp.json['data'][0]['form_option_option_date'] == '2020-01-15'
1687
    resp2 = get_app(pub).get(sign_uri('/api/user/forms?&full=on', user=local_user))
1688
    assert resp.json == resp2.json
1689

  
1690
    formdef.disabled = True
1691
    formdef.store()
1692
    resp = get_app(pub).get(sign_uri('/api/user/forms', user=local_user))
1693
    assert resp.json['err'] == 0
1694
    assert len(resp.json['data']) == 1
1695

  
1696
    # check digest is part of contents
1697
    formdef.digest_template = 'XYZ'
1698
    formdef.data_class().get(formdata.id).store()
1699
    assert formdef.data_class().get(formdata.id).digest == 'XYZ'
1700
    resp = get_app(pub).get(sign_uri('/api/user/forms', user=local_user))
1701
    assert resp.json['data'][0]['form_digest'] == 'XYZ'
1702

  
1703
    resp = get_app(pub).get(sign_uri('/api/user/forms?NameID=xxx'))
1704
    assert resp.json == {'err': 1, 'err_desc': 'unknown NameID', 'data': []}
1705
    resp2 = get_app(pub).get(sign_uri('/api/user/forms?&NameID=xxx'))
1706
    assert resp.json == resp2.json
1707

  
1708
    formdata = formdef.data_class()()
1709
    formdata.user_id = local_user.id
1710
    formdata.status = 'draft'
1711
    formdata.receipt_time = datetime.datetime(2015, 1, 1).timetuple()
1712
    formdata.store()
1713

  
1714
    resp = get_app(pub).get(sign_uri('/api/user/forms', user=local_user))
1715
    assert resp.json['err'] == 0
1716
    assert len(resp.json['data']) == 1
1717

  
1718
    resp = get_app(pub).get(sign_uri('/api/user/forms?include-drafts=true', user=local_user))
1719
    assert resp.json['err'] == 0
1720
    assert len(resp.json['data']) == 1
1721

  
1722
    formdef.disabled = False
1723
    formdef.store()
1724

  
1725
    resp = get_app(pub).get(sign_uri('/api/user/forms?include-drafts=true', user=local_user))
1726
    assert resp.json['err'] == 0
1727
    assert len(resp.json['data']) == 2
1728

  
1729
    draft_formdata = [x for x in resp.json['data'] if x['status'] == 'Draft'][0]
1730

  
1731
    formdata = formdef.data_class()()
1732
    formdata.data = {'0': 'foo@localhost', '1': 'xyy'}
1733
    formdata.user_id = local_user.id
1734
    formdata.just_created()
1735
    formdata.receipt_time = (datetime.datetime.now() + datetime.timedelta(days=1)).timetuple()
1736
    formdata.jump_status('new')
1737
    formdata.store()
1738

  
1739
    resp = get_app(pub).get(sign_uri('/api/user/forms', user=local_user))
1740
    assert len(resp.json['data']) == 2
1741
    resp2 = get_app(pub).get(sign_uri('/api/user/forms?sort=desc', user=local_user))
1742
    assert len(resp2.json['data']) == 2
1743
    assert resp2.json['data'][0] == resp.json['data'][1]
1744
    assert resp2.json['data'][1] == resp.json['data'][0]
1745

  
1746

  
1747
def test_user_forms_limit_offset(pub, local_user):
1748
    if not pub.is_using_postgresql():
1749
        pytest.skip('this requires SQL')
1750
        return
1751

  
1752
    FormDef.wipe()
1753
    formdef = FormDef()
1754
    formdef.name = 'test limit offset'
1755
    formdef.fields = [
1756
        fields.StringField(id='0', label='foobar', varname='foobar'),
1757
        fields.StringField(id='1', label='foobar2'),
1758
    ]
1759
    formdef.keywords = 'hello, world'
1760
    formdef.disabled = False
1761
    formdef.enable_tracking_codes = False
1762
    formdef.store()
1763
    formdef.data_class().wipe()
1764

  
1765
    for i in range(50):
1766
        formdata = formdef.data_class()()
1767
        formdata.data = {'0': 'foo@localhost', '1': str(i)}
1768
        formdata.user_id = local_user.id
1769
        formdata.just_created()
1770
        formdata.receipt_time = (datetime.datetime.now() + datetime.timedelta(days=i)).timetuple()
1771
        formdata.jump_status('new')
1772
        formdata.store()
1773

  
1774
    resp = get_app(pub).get(sign_uri('/api/users/%s/forms' % local_user.id))
1775
    assert resp.json['err'] == 0
1776
    assert len(resp.json['data']) == 50
1777

  
1778
    resp = get_app(pub).get(sign_uri('/api/users/%s/forms?limit=10' % local_user.id))
1779
    assert resp.json['err'] == 0
1780
    assert len(resp.json['data']) == 10
1781
    assert [x['form_number_raw'] for x in resp.json['data']] == [str(x) for x in range(1, 11)]
1782

  
1783
    resp = get_app(pub).get(sign_uri('/api/users/%s/forms?limit=10&offset=45' % local_user.id))
1784
    assert resp.json['err'] == 0
1785
    assert len(resp.json['data']) == 5
1786
    assert [x['form_number_raw'] for x in resp.json['data']] == [str(x) for x in range(46, 51)]
1787

  
1788
    resp = get_app(pub).get(sign_uri('/api/users/%s/forms?limit=10&sort=desc' % local_user.id))
1789
    assert resp.json['err'] == 0
1790
    assert len(resp.json['data']) == 10
1791
    assert [x['form_number_raw'] for x in resp.json['data']] == [str(x) for x in range(50, 40, -1)]
1792

  
1793

  
1794
def test_user_forms_from_agent(pub, local_user):
1795
    Role.wipe()
1796
    role = Role(name='Foo bar')
1797
    role.store()
1798

  
1799
    agent_user = get_publisher().user_class()
1800
    agent_user.name = 'Agent'
1801
    agent_user.email = 'agent@example.com'
1802
    agent_user.name_identifiers = ['ABCDE']
1803
    agent_user.roles = [role.id]
1804
    agent_user.store()
1805

  
1806
    FormDef.wipe()
1807
    formdef = FormDef()
1808
    formdef.name = 'test'
1809
    formdef.fields = [
1810
        fields.StringField(id='0', label='foobar', varname='foobar'),
1811
        fields.StringField(id='1', label='foobar2'),
1812
    ]
1813
    formdef.store()
1814
    formdef.data_class().wipe()
1815

  
1816
    formdata = formdef.data_class()()
1817
    formdata.data = {'0': 'foo@localhost', '1': 'xxx'}
1818
    formdata.user_id = local_user.id
1819
    formdata.just_created()
1820
    formdata.jump_status('new')
1821
    formdata.store()
1822

  
1823
    resp = get_app(pub).get(sign_uri('/api/users/%s/forms' % local_user.id, user=agent_user))
1824
    assert resp.json['err'] == 0
1825
    assert len(resp.json['data']) == 1
1826
    assert resp.json['data'][0]['form_name'] == 'test'
1827
    assert resp.json['data'][0]['form_slug'] == 'test'
1828
    assert resp.json['data'][0]['form_status'] == 'New'
1829
    assert resp.json['data'][0]['readable'] is False
1830

  
1831
    formdef.skip_from_360_view = True
1832
    formdef.store()
1833

  
1834
    resp = get_app(pub).get(sign_uri('/api/users/%s/forms' % local_user.id, user=agent_user))
1835
    assert len(resp.json['data']) == 0
1836

  
1837
    formdef.workflow_roles = {'_receiver': str(role.id)}
1838
    formdef.store()
1839
    formdef.data_class().rebuild_security()
1840
    resp = get_app(pub).get(sign_uri('/api/users/%s/forms' % local_user.id, user=agent_user))
1841
    assert len(resp.json['data']) == 1
1842

  
1843
    agent_user.roles = []
1844
    agent_user.store()
1845
    get_app(pub).get(sign_uri('/api/users/%s/forms' % local_user.id, user=agent_user), status=403)
1846

  
1847

  
1848
def test_user_drafts(pub, local_user):
1849
    FormDef.wipe()
1850
    formdef = FormDef()
1851
    formdef.name = 'test'
1852
    formdef.fields = [
1853
        fields.StringField(id='0', label='foobar', varname='foobar'),
1854
        fields.StringField(id='1', label='foobar2'),
1855
        fields.FileField(id='2', label='foobar3', varname='file'),
1856
    ]
1857
    formdef.keywords = 'hello, world'
1858
    formdef.disabled = False
1859
    formdef.enable_tracking_codes = True
1860
    formdef.store()
1861

  
1862
    formdef.data_class().wipe()
1863

  
1864
    resp = get_app(pub).get(sign_uri('/api/user/drafts', user=local_user))
1865
    assert resp.json['err'] == 0
1866
    assert len(resp.json['data']) == 0
1867

  
1868
    formdata = formdef.data_class()()
1869
    upload = PicklableUpload('test.txt', 'text/plain', 'ascii')
1870
    upload.receive([b'base64me'])
1871
    formdata.data = {'0': 'foo@localhost', '1': 'xxx', '2': upload}
1872
    formdata.user_id = local_user.id
1873
    formdata.page_no = 1
1874
    formdata.status = 'draft'
1875
    formdata.receipt_time = datetime.datetime(2015, 1, 1).timetuple()
1876
    formdata.store()
1877

  
1878
    resp = get_app(pub).get(sign_uri('/api/user/drafts', user=local_user))
1879
    resp2 = get_app(pub).get(sign_uri('/myspace/drafts', user=local_user))
1880
    assert resp.json['err'] == 0
1881
    assert len(resp.json['data']) == 1
1882
    assert resp.json == resp2.json
1883
    assert not 'fields' in resp.json['data'][0]
1884
    assert resp.json['data'][0]['keywords'] == ['hello', 'world']
1885

  
1886
    resp = get_app(pub).get(sign_uri('/api/user/drafts?full=on', user=local_user))
1887
    assert resp.json['err'] == 0
1888
    assert 'fields' in resp.json['data'][0]
1889
    assert resp.json['data'][0]['fields']['foobar'] == 'foo@localhost'
1890
    assert 'url' in resp.json['data'][0]['fields']['file']
1891
    assert 'content' not in resp.json['data'][0]['fields']['file']  # no file content in full lists
1892
    assert resp.json['data'][0]['keywords'] == ['hello', 'world']
1893

  
1894
    formdef.enable_tracking_codes = False
1895
    formdef.store()
1896
    resp = get_app(pub).get(sign_uri('/api/user/drafts', user=local_user))
1897
    assert resp.json['err'] == 0
1898
    assert len(resp.json['data']) == 1
1899

  
1900
    formdef.enable_tracking_codes = True
1901
    formdef.disabled = True
1902
    formdef.store()
1903
    resp = get_app(pub).get(sign_uri('/api/user/drafts', user=local_user))
1904
    assert resp.json['err'] == 0
1905
    assert len(resp.json['data']) == 0
1906

  
1907
    resp = get_app(pub).get(sign_uri('/api/user/drafts?NameID=xxx'))
1908
    assert resp.json == {'err': 1, 'err_desc': 'unknown NameID', 'data': []}
1909
    resp2 = get_app(pub).get(sign_uri('/api/user/drafts?&NameID=xxx'))
1910
    assert resp.json == resp2.json
1911

  
1912

  
1913
def test_api_list_formdata(pub, local_user):
1914
    Role.wipe()
1915
    role = Role(name='test')
1916
    role.store()
1917

  
1918
    FormDef.wipe()
1919
    formdef = FormDef()
1920
    formdef.name = 'test'
1921
    formdef.workflow_roles = {'_receiver': role.id}
1922
    formdef.fields = [
1923
        fields.StringField(id='0', label='foobar', varname='foobar', type='string'),
1924
        fields.ItemField(
1925
            id='1', label='foobar3', varname='foobar3', type='item', items=['foo', 'bar', 'baz']
1926
        ),
1927
        fields.FileField(id='2', label='foobar4', varname='file', type='file'),
1928
    ]
1929
    formdef.store()
1930

  
1931
    data_class = formdef.data_class()
1932
    data_class.wipe()
1933

  
1934
    for i in range(30):
1935
        formdata = data_class()
1936
        date = time.strptime('2014-01-20', '%Y-%m-%d')
1937
        upload = PicklableUpload('test.txt', 'text/plain', 'ascii')
1938
        upload.receive([b'base64me'])
1939
        formdata.data = {'0': 'FOO BAR %d' % i, '2': upload}
1940
        formdata.user_id = local_user.id
1941
        if i % 4 == 0:
1942
            formdata.data['1'] = 'foo'
1943
            formdata.data['1_display'] = 'foo'
1944
        elif i % 4 == 1:
1945
            formdata.data['1'] = 'bar'
1946
            formdata.data['1_display'] = 'bar'
1947
        else:
1948
            formdata.data['1'] = 'baz'
1949
            formdata.data['1_display'] = 'baz'
1950

  
1951
        formdata.just_created()
1952
        if i % 3 == 0:
1953
            formdata.jump_status('new')
1954
        elif i % 3 == 1:
1955
            formdata.jump_status('just_submitted')
1956
        else:
1957
            formdata.jump_status('finished')
1958
        if i % 7 == 0:
1959
            formdata.backoffice_submission = True
1960
            formdata.submission_channel = 'mail'
1961
        formdata.evolution[-1].time = (
1962
            datetime.datetime(2020, 1, 2, 3, 4) + datetime.timedelta(hours=i)
1963
        ).timetuple()
1964
        formdata.store()
1965

  
1966
    # check access is denied if the user has not the appropriate role
1967
    resp = get_app(pub).get(sign_uri('/api/forms/test/list', user=local_user), status=403)
1968

  
1969
    # add proper role to user
1970
    local_user.roles = [role.id]
1971
    local_user.store()
1972

  
1973
    # check it now gets the data
1974
    resp = get_app(pub).get(sign_uri('/api/forms/test/list', user=local_user))
1975
    assert len(resp.json) == 30
1976
    assert datetime.datetime.strptime(resp.json[0]['receipt_time'], '%Y-%m-%dT%H:%M:%S')
1977
    assert not 'fields' in resp.json[0]
1978

  
1979
    # check getting full formdata
1980
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?full=on', user=local_user))
1981
    assert len(resp.json) == 30
1982
    assert 'receipt_time' in resp.json[0]
1983
    assert 'fields' in resp.json[0]
1984
    assert 'url' in resp.json[0]['fields']['file']
1985
    assert 'content' not in resp.json[0]['fields']['file']  # no file content in full lists
1986
    assert 'user' in resp.json[0]
1987
    assert 'evolution' in resp.json[0]
1988
    assert len(resp.json[0]['evolution']) == 2
1989
    assert 'status' in resp.json[0]['evolution'][0]
1990
    assert 'who' in resp.json[0]['evolution'][0]
1991
    assert 'time' in resp.json[0]['evolution'][0]
1992
    assert resp.json[0]['evolution'][0]['who']['id'] == local_user.id
1993

  
1994
    assert all('status' in x['workflow'] for x in resp.json)
1995
    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 0'][0]['submission'][
1996
        'backoffice'
1997
    ] is True
1998
    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 0'][0]['submission'][
1999
        'channel'
2000
    ] == 'mail'
2001
    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 1'][0]['submission'][
2002
        'backoffice'
2003
    ] is False
2004
    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 1'][0]['submission']['channel'] == 'web'
2005

  
2006
    # check filtered results
2007
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter-foobar3=foo', user=local_user))
2008
    assert len(resp.json) == 8
2009
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter-foobar3=bar', user=local_user))
2010
    assert len(resp.json) == 8
2011
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter-foobar3=baz', user=local_user))
2012
    assert len(resp.json) == 14
2013

  
2014
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter-foobar=FOO BAR 3', user=local_user))
2015
    assert len(resp.json) == 1
2016

  
2017
    # check filter on status
2018
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter=pending', user=local_user))
2019
    assert len(resp.json) == 20
2020
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter=done', user=local_user))
2021
    assert len(resp.json) == 10
2022
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter=all', user=local_user))
2023
    assert len(resp.json) == 30
2024

  
2025
    # check filter on last update time
2026
    resp = get_app(pub).get(
2027
        sign_uri(
2028
            '/api/forms/test/list?filter-start-mtime=on&filter-start-mtime-value=2020-01-03', user=local_user
2029
        )
2030
    )
2031
    assert len(resp.json) == 16
2032
    resp = get_app(pub).get(
2033
        sign_uri(
2034
            '/api/forms/test/list?filter-start-mtime=on&filter-start-mtime-value=2020-01-03 10:00',
2035
            user=local_user,
2036
        )
2037
    )
2038
    assert len(resp.json) == 10
2039
    resp = get_app(pub).get(
2040
        sign_uri(
2041
            '/api/forms/test/list?filter-end-mtime=on&filter-end-mtime-value=2020-01-03', user=local_user
2042
        )
2043
    )
2044
    assert len(resp.json) == 14
2045
    resp = get_app(pub).get(
2046
        sign_uri(
2047
            '/api/forms/test/list?filter-end-mtime=on&filter-end-mtime-value=2020-01-03 10:00',
2048
            user=local_user,
2049
        )
2050
    )
2051
    assert len(resp.json) == 20
2052

  
2053
    # check limit and offset
2054
    resp_all = get_app(pub).get(sign_uri('/api/forms/test/list?filter=all', user=local_user))
2055
    assert len(resp_all.json) == 30
2056
    partial_resps = []
2057
    for i in range(0, 48, 12):
2058
        partial_resps.append(
2059
            get_app(pub).get(
2060
                sign_uri('/api/forms/test/list?filter=all&offset=%s&limit=12' % i, user=local_user)
2061
            )
2062
        )
2063
    assert len(partial_resps[0].json) == 12
2064
    assert len(partial_resps[1].json) == 12
2065
    assert len(partial_resps[2].json) == 6
2066
    assert len(partial_resps[3].json) == 0
2067
    resp_all_ids = [x.get('id') for x in resp_all.json]
2068
    resp_partial_ids = []
2069
    for resp in partial_resps:
2070
        resp_partial_ids.extend([x.get('id') for x in resp.json])
2071
    assert resp_all_ids == resp_partial_ids
2072

  
2073
    # check error handling
2074
    get_app(pub).get(sign_uri('/api/forms/test/list?filter=all&offset=plop', user=local_user), status=400)
2075
    get_app(pub).get(sign_uri('/api/forms/test/list?filter=all&limit=plop', user=local_user), status=400)
2076

  
2077

  
2078
def test_api_anonymized_formdata(pub, local_user, admin_user):
2079
    Role.wipe()
2080
    role = Role(name='test')
2081
    role.store()
2082

  
2083
    FormDef.wipe()
2084
    formdef = FormDef()
2085
    formdef.name = 'test'
2086
    formdef.workflow_roles = {'_receiver': role.id}
2087
    formdef.fields = [
2088
        fields.StringField(id='0', label='foobar', varname='foobar'),
2089
        fields.ItemField(
2090
            id='1', label='foobar3', varname='foobar3', type='item', items=['foo', 'bar', 'baz']
2091
        ),
2092
        fields.FileField(id='2', label='foobar4', varname='file'),
2093
    ]
2094
    formdef.store()
2095

  
2096
    data_class = formdef.data_class()
2097
    data_class.wipe()
2098

  
2099
    for i in range(30):
2100
        formdata = data_class()
2101
        date = time.strptime('2014-01-20', '%Y-%m-%d')
2102
        upload = PicklableUpload('test.txt', 'text/plain', 'ascii')
2103
        upload.receive([b'base64me'])
2104
        formdata.data = {'0': 'FOO BAR %d' % i, '2': upload}
2105
        formdata.user_id = local_user.id
2106
        if i % 4 == 0:
2107
            formdata.data['1'] = 'foo'
2108
            formdata.data['1_display'] = 'foo'
2109
        elif i % 4 == 1:
2110
            formdata.data['1'] = 'bar'
2111
            formdata.data['1_display'] = 'bar'
2112
        else:
2113
            formdata.data['1'] = 'baz'
2114
            formdata.data['1_display'] = 'baz'
2115

  
2116
        formdata.just_created()
2117
        if i % 3 == 0:
2118
            formdata.jump_status('new')
2119
        else:
2120
            evo = Evolution()
2121
            evo.who = admin_user.id
2122
            evo.time = time.localtime()
2123
            evo.status = 'wf-%s' % 'finished'
2124
            formdata.evolution.append(evo)
2125
            formdata.status = evo.status
2126
        formdata.store()
2127

  
2128
    # check access is granted even if the user has not the appropriate role
2129
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?anonymise&full=on', user=local_user))
2130
    assert len(resp.json) == 30
2131
    assert 'receipt_time' in resp.json[0]
2132
    assert 'fields' in resp.json[0]
2133
    assert 'user' not in resp.json[0]
2134
    assert 'file' not in resp.json[0]['fields']  # no file export in full lists
2135
    assert 'foobar3' in resp.json[0]['fields']
2136
    assert 'foobar' not in resp.json[0]['fields']
2137
    assert 'evolution' in resp.json[0]
2138
    assert len(resp.json[0]['evolution']) == 2
2139
    assert 'status' in resp.json[0]['evolution'][0]
2140
    assert not 'who' in resp.json[0]['evolution'][0]
2141
    assert 'time' in resp.json[0]['evolution'][0]
2142
    # check evolution made by other than _submitter are exported
2143
    assert 'who' in resp.json[1]['evolution'][1]
2144
    assert 'id' in resp.json[1]['evolution'][1]['who']
2145
    assert 'email' in resp.json[1]['evolution'][1]['who']
2146
    assert 'NameID' in resp.json[1]['evolution'][1]['who']
2147
    assert 'name' in resp.json[1]['evolution'][1]['who']
2148

  
2149
    # check access is granted event if there is no user
2150
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?anonymise&full=on'))
2151
    assert len(resp.json) == 30
2152
    assert 'receipt_time' in resp.json[0]
2153
    assert 'fields' in resp.json[0]
2154
    assert 'user' not in resp.json[0]
2155
    assert 'file' not in resp.json[0]['fields']  # no file export in full lists
2156
    assert 'foobar3' in resp.json[0]['fields']
2157
    assert 'foobar' not in resp.json[0]['fields']
2158
    assert 'evolution' in resp.json[0]
2159
    assert len(resp.json[0]['evolution']) == 2
2160
    assert 'status' in resp.json[0]['evolution'][0]
2161
    assert not 'who' in resp.json[0]['evolution'][0]
2162
    assert 'time' in resp.json[0]['evolution'][0]
2163
    # check anonymise is enforced on detail view
2164
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/?anonymise&full=on' % resp.json[1]['id']))
2165
    assert 'receipt_time' in resp.json
2166
    assert 'fields' in resp.json
2167
    assert 'user' not in resp.json
2168
    assert 'file' not in resp.json['fields']  # no file export in detail
2169
    assert 'foobar3' in resp.json['fields']
2170
    assert 'foobar' not in resp.json['fields']
2171
    assert 'evolution' in resp.json
2172
    assert len(resp.json['evolution']) == 2
2173
    assert 'status' in resp.json['evolution'][0]
2174
    assert not 'who' in resp.json['evolution'][0]
2175
    assert 'time' in resp.json['evolution'][0]
2176
    # check evolution made by other than _submitter are exported
2177
    assert 'who' in resp.json['evolution'][1]
2178
    assert 'id' in resp.json['evolution'][1]['who']
2179
    assert 'email' in resp.json['evolution'][1]['who']
2180
    assert 'NameID' in resp.json['evolution'][1]['who']
2181
    assert 'name' in resp.json['evolution'][1]['who']
2182

  
2183

  
2184
def test_api_geojson_formdata(pub, local_user):
2185
    Role.wipe()
2186
    role = Role(name='test')
2187
    role.store()
2188

  
2189
    FormDef.wipe()
2190
    formdef = FormDef()
2191
    formdef.name = 'test'
2192
    formdef.workflow_roles = {'_receiver': role.id}
2193
    formdef.fields = [
2194
        fields.StringField(id='0', label='foobar', varname='foobar', type='string'),
2195
        fields.FileField(id='1', label='foobar1', type='file'),
2196
    ]
2197
    formdef.store()
2198

  
2199
    data_class = formdef.data_class()
2200
    data_class.wipe()
2201

  
2202
    formdef.geolocations = {'base': 'Location'}
2203
    formdef.store()
2204

  
2205
    # check access is denied if the user has not the appropriate role
2206
    resp = get_app(pub).get(sign_uri('/api/forms/test/geojson', user=local_user), status=403)
2207
    # even if there's an anonymse parameter
2208
    resp = get_app(pub).get(sign_uri('/api/forms/test/geojson?anonymise', user=local_user), status=403)
2209

  
2210
    upload = PicklableUpload('test.txt', 'text/plain', 'ascii')
2211
    upload.receive([b'base64me'])
2212

  
2213
    foobar = '<font color="red">FOO BAR</font>'
2214
    username = '<font color="red">Jean Darmette</font>'
2215

  
2216
    data = {'0': foobar, '1': upload}
2217
    local_user.name = username
2218
    local_user.store()
2219
    for i in range(30):
2220
        formdata = data_class()
2221
        date = time.strptime('2014-01-20', '%Y-%m-%d')
2222
        formdata.geolocations = {'base': {'lat': 48, 'lon': 2}}
2223
        formdata.data = data
2224
        formdata.user_id = local_user.id
2225
        formdata.just_created()
2226
        if i % 3 == 0:
2227
            formdata.jump_status('new')
2228
        else:
2229
            formdata.jump_status('finished')
2230
        formdata.store()
2231

  
2232
    # add proper role to user
2233
    local_user.roles = [role.id]
2234
    local_user.store()
2235

  
2236
    # check it gets the data
2237
    resp = get_app(pub).get(sign_uri('/api/forms/test/geojson', user=local_user))
2238
    assert 'features' in resp.json
2239
    assert len(resp.json['features']) == 10
2240
    display_fields = resp.json['features'][0]['properties']['display_fields']
2241
    assert len(display_fields) == 5
2242
    for field in display_fields:
2243
        if field['label'] == 'Number':
2244
            assert field['varname'] == 'id'
2245
            assert field['html_value'] == '1-28'
2246
            assert field['value'] == '1-28'
2247
        if field['label'] == 'User Label':
2248
            assert field['varname'] == 'user_label'
2249
            assert field['value'] == username
2250
            assert field['html_value'] == "&lt;font color=&quot;red&quot;&gt;Jean Darmette&lt;/font&gt;"
2251
        if field['label'] == 'foobar':
2252
            assert field['varname'] == 'foobar'
2253
            assert field['value'] == foobar
2254
            assert field['html_value'] == "&lt;font color=&quot;red&quot;&gt;FOO BAR&lt;/font&gt;"
2255
        if field['label'] == 'foobar1':
2256
            assert field['varname'] is None
2257
            assert field['value'] == "test.txt"
2258
            assert (
2259
                field['html_value']
2260
                == '<div class="file-field"><a download="test.txt" href="http://example.net/backoffice/management/test/28/download?f=1"><span>test.txt</span></a></div>'
2261
            )
2262
    field_varnames = [f['varname'] for f in display_fields]
2263
    assert 'foobar' not in field_varnames
2264

  
2265
    # check full=on
2266
    resp = get_app(pub).get(sign_uri('/api/forms/test/geojson?full=on', user=local_user))
2267
    assert len(resp.json['features']) == 10
2268
    display_fields = resp.json['features'][0]['properties']['display_fields']
2269
    assert len(display_fields) == 8
2270
    field_varnames = [f['varname'] for f in display_fields]
2271
    assert 'foobar' in field_varnames
2272

  
2273
    # check with a filter
2274
    resp = get_app(pub).get(sign_uri('/api/forms/test/geojson?filter=done', user=local_user))
2275
    assert 'features' in resp.json
2276
    assert len(resp.json['features']) == 20
2277

  
2278
    # check with http basic auth
2279
    app = get_app(pub)
2280
    app.authorization = ('Basic', ('user', 'password'))
2281
    resp = app.get('/api/forms/test/geojson?email=%s' % local_user.email, status=401)
2282

  
2283
    # add authentication info
2284
    pub.load_site_options()
2285
    pub.site_options.add_section('api-http-auth-geojson')
2286
    pub.site_options.set('api-http-auth-geojson', 'user', 'password')
2287
    pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
2288

  
2289
    resp = app.get('/api/forms/test/geojson?email=%s' % local_user.email)
2290
    assert 'features' in resp.json
2291
    assert len(resp.json['features']) == 10
2292

  
2293
    # check 404 if the formdef doesn't have geolocation support
2294
    formdef.geolocations = {}
2295
    formdef.store()
2296
    resp = get_app(pub).get(sign_uri('/api/forms/test/geojson', user=local_user), status=404)
2297

  
2298

  
2299
def test_api_ods_formdata(pub, local_user):
2300
    Role.wipe()
2301
    role = Role(name='test')
2302
    role.store()
2303

  
2304
    FormDef.wipe()
2305
    formdef = FormDef()
2306
    formdef.name = 'test'
2307
    formdef.workflow_roles = {'_receiver': role.id}
2308
    formdef.fields = [
2309
        fields.StringField(id='0', label='foobar', varname='foobar', type='string'),
2310
    ]
2311
    formdef.store()
2312

  
2313
    data_class = formdef.data_class()
2314
    data_class.wipe()
2315

  
2316
    # check access is denied if the user has not the appropriate role
2317
    resp = get_app(pub).get(sign_uri('/api/forms/test/ods', user=local_user), status=403)
2318
    # even if there's an anonymise parameter
2319
    resp = get_app(pub).get(sign_uri('/api/forms/test/ods?anonymise', user=local_user), status=403)
2320

  
2321
    data = {'0': 'foobar'}
2322
    for i in range(30):
2323
        formdata = data_class()
2324
        formdata.data = data
2325
        formdata.user_id = local_user.id
2326
        formdata.just_created()
2327
        if i % 3 == 0:
2328
            formdata.jump_status('new')
2329
        else:
2330
            formdata.jump_status('finished')
2331
        formdata.store()
2332

  
2333
    # add proper role to user
2334
    local_user.roles = [role.id]
2335
    local_user.store()
2336

  
2337
    # check it gets the data
2338
    resp = get_app(pub).get(sign_uri('/api/forms/test/ods', user=local_user))
2339
    assert resp.content_type == 'application/vnd.oasis.opendocument.spreadsheet'
2340

  
2341
    # check it still gives a ods file when there is more data
2342
    for i in range(300):
2343
        formdata = data_class()
2344
        formdata.data = data
2345
        formdata.user_id = local_user.id
2346
        formdata.just_created()
2347
        formdata.jump_status('new')
2348
        formdata.store()
2349

  
2350
    resp = get_app(pub).get(sign_uri('/api/forms/test/ods', user=local_user))
2351
    assert resp.content_type == 'application/vnd.oasis.opendocument.spreadsheet'
2352
    zipf = zipfile.ZipFile(BytesIO(resp.body))
2353
    ods_sheet = ET.parse(zipf.open('content.xml'))
2354
    assert len(ods_sheet.findall('.//{%s}table-row' % ods.NS['table'])) == 311
2355

  
2356

  
2357
def test_api_custom_view_access(pub, local_user):
2358
    Role.wipe()
2359
    role = Role(name='test')
2360
    role.store()
2361
    local_user.roles = [role.id]
2362
    local_user.store()
2363

  
2364
    FormDef.wipe()
2365
    formdef = FormDef()
2366
    formdef.name = 'test'
2367
    formdef.workflow_roles = {'_receiver': role.id}
2368
    formdef.fields = [fields.StringField(id='0', label='foobar', varname='foobar')]
2369
    formdef.geolocations = {'base': 'Location'}
2370
    formdef.store()
2371

  
2372
    carddef = CardDef()
2373
    carddef.name = 'test'
2374
    carddef.fields = [fields.StringField(id='0', label='foobar', varname='foo')]
2375
    carddef.workflow_roles = {'_viewer': role.id}
2376
    carddef.digest_template = 'bla {{ form_var_foo }} xxx'
2377
    carddef.geolocations = {'base': 'Location'}
2378
    carddef.store()
2379

  
2380
    pub.custom_view_class.wipe()
2381
    custom_view = pub.custom_view_class()
2382
    custom_view.title = 'shared formdef custom view'
2383
    custom_view.formdef = formdef
2384
    custom_view.columns = {'list': [{'id': '0'}]}
2385
    custom_view.filters = {}
2386
    custom_view.visibility = 'any'
2387
    custom_view.store()
2388

  
2389
    custom_view = pub.custom_view_class()
2390
    custom_view.title = 'private formdef custom view'
2391
    custom_view.formdef = formdef
2392
    custom_view.columns = {'list': [{'id': '0'}]}
2393
    custom_view.filters = {}
2394
    custom_view.visibility = 'owner'
2395
    custom_view.user = local_user
2396
    custom_view.store()
2397

  
2398
    custom_view = pub.custom_view_class()
2399
    custom_view.title = 'shared carddef custom view'
2400
    custom_view.formdef = carddef
2401
    custom_view.columns = {'list': [{'id': '0'}]}
2402
    custom_view.filters = {}
2403
    custom_view.visibility = 'any'
2404
    custom_view.store()
2405

  
2406
    custom_view = pub.custom_view_class()
2407
    custom_view.title = 'private carddef custom view'
2408
    custom_view.formdef = carddef
2409
    custom_view.columns = {'list': [{'id': '0'}]}
2410
    custom_view.filters = {}
2411
    custom_view.visibility = 'owner'
2412
    custom_view.user = local_user
2413
    custom_view.store()
2414

  
2415
    custom_view = pub.custom_view_class()
2416
    custom_view.title = 'datasource carddef custom view'
2417
    custom_view.formdef = carddef
2418
    custom_view.columns = {'list': [{'id': '0'}]}
2419
    custom_view.filters = {}
2420
    custom_view.visibility = 'datasource'
2421
    custom_view.store()
2422

  
2423
    get_app(pub).get(sign_uri('/api/forms/test/list/shared-formdef-custom-view', user=local_user), status=200)
2424
    get_app(pub).get(sign_uri('/api/forms/test/ods/shared-formdef-custom-view', user=local_user), status=200)
2425
    get_app(pub).get(
2426
        sign_uri('/api/forms/test/geojson/shared-formdef-custom-view', user=local_user), status=200
2427
    )
2428
    get_app(pub).get(
2429
        sign_uri('/api/forms/test/list/private-formdef-custom-view', user=local_user), status=404
2430
    )
2431
    get_app(pub).get(sign_uri('/api/forms/test/ods/private-formdef-custom-view', user=local_user), status=404)
2432
    get_app(pub).get(
2433
        sign_uri('/api/forms/test/geojson/private-formdef-custom-view', user=local_user), status=404
2434
    )
2435

  
2436
    get_app(pub).get(sign_uri('/api/cards/test/list/shared-carddef-custom-view', user=local_user), status=200)
2437
    get_app(pub).get(sign_uri('/api/cards/test/ods/shared-carddef-custom-view', user=local_user), status=200)
2438
    get_app(pub).get(
2439
        sign_uri('/api/cards/test/geojson/shared-carddef-custom-view', user=local_user), status=200
2440
    )
2441
    get_app(pub).get(
2442
        sign_uri('/api/cards/test/list/private-carddef-custom-view', user=local_user), status=404
2443
    )
2444
    get_app(pub).get(sign_uri('/api/cards/test/ods/private-carddef-custom-view', user=local_user), status=404)
2445
    get_app(pub).get(
2446
        sign_uri('/api/cards/test/geojson/private-carddef-custom-view', user=local_user), status=404
2447
    )
2448
    get_app(pub).get(
2449
        sign_uri('/api/cards/test/list/datasource-carddef-custom-view', user=local_user), status=200
2450
    )
2451
    get_app(pub).get(
2452
        sign_uri('/api/cards/test/ods/datasource-carddef-custom-view', user=local_user), status=200
2453
    )
2454
    get_app(pub).get(
2455
        sign_uri('/api/cards/test/geojson/datasource-carddef-custom-view', user=local_user), status=200
2456
    )
2457

  
2458

  
2459
def test_api_list_formdata_custom_view(pub, local_user):
2460
    Role.wipe()
2461
    role = Role(name='test')
2462
    role.store()
2463

  
2464
    FormDef.wipe()
2465
    formdef = FormDef()
2466
    formdef.name = 'test'
2467
    formdef.workflow_roles = {'_receiver': role.id}
2468
    formdef.fields = [
2469
        fields.StringField(id='0', label='foobar', varname='foobar'),
2470
    ]
2471
    formdef.store()
2472

  
2473
    data_class = formdef.data_class()
2474
    data_class.wipe()
2475

  
2476
    for i in range(30):
2477
        formdata = data_class()
2478
        formdata.data = {'0': 'FOO BAR %d' % i}
2479
        formdata.user_id = local_user.id
2480
        formdata.just_created()
2481
        if i % 3 == 0:
2482
            formdata.jump_status('new')
2483
        else:
2484
            formdata.jump_status('finished')
2485
        formdata.store()
2486

  
2487
    # add proper role to user
2488
    local_user.roles = [role.id]
2489
    local_user.store()
2490

  
2491
    # check it now gets the data
2492
    resp = get_app(pub).get(sign_uri('/api/forms/test/list', user=local_user))
2493
    assert len(resp.json) == 30
2494

  
2495
    pub.custom_view_class.wipe()
2496
    custom_view = pub.custom_view_class()
2497
    custom_view.title = 'custom view'
2498
    custom_view.formdef = formdef
2499
    custom_view.columns = {'list': [{'id': '0'}]}
2500
    custom_view.filters = {"filter": "done", "filter-status": "on"}
2501
    custom_view.visibility = 'any'
2502
    custom_view.store()
2503

  
2504
    resp = get_app(pub).get(sign_uri('/api/forms/test/list/custom-view', user=local_user))
2505
    assert len(resp.json['data']) == 20
2506

  
2507

  
2508
def test_api_ods_formdata_custom_view(pub, local_user):
2509
    Role.wipe()
2510
    role = Role(name='test')
2511
    role.store()
2512

  
2513
    FormDef.wipe()
2514
    formdef = FormDef()
2515
    formdef.name = 'test'
2516
    formdef.workflow_roles = {'_receiver': role.id}
2517
    formdef.fields = [
2518
        fields.StringField(id='0', label='foobar', varname='foobar'),
2519
    ]
2520
    formdef.store()
2521

  
2522
    data_class = formdef.data_class()
2523
    data_class.wipe()
2524

  
2525
    for i in range(30):
2526
        formdata = data_class()
2527
        formdata.data = {'0': 'FOO BAR %d' % i}
2528
        formdata.user_id = local_user.id
2529
        formdata.just_created()
2530
        if i % 3 == 0:
2531
            formdata.jump_status('new')
2532
        else:
2533
            formdata.jump_status('finished')
2534
        formdata.store()
2535

  
2536
    # add proper role to user
2537
    local_user.roles = [role.id]
2538
    local_user.store()
2539

  
2540
    # check it now gets the data
2541
    resp = get_app(pub).get(sign_uri('/api/forms/test/ods', user=local_user))
2542
    zipf = zipfile.ZipFile(BytesIO(resp.body))
2543
    ods_sheet = ET.parse(zipf.open('content.xml'))
2544
    assert len(ods_sheet.findall('.//{%s}table-row' % ods.NS['table'])) == 11
2545

  
2546
    pub.custom_view_class.wipe()
2547
    custom_view = pub.custom_view_class()
2548
    custom_view.title = 'custom view'
2549
    custom_view.formdef = formdef
2550
    custom_view.columns = {'list': [{'id': '0'}]}
2551
    custom_view.filters = {"filter": "done", "filter-status": "on"}
2552
    custom_view.visibility = 'any'
2553
    custom_view.store()
2554

  
2555
    resp = get_app(pub).get(sign_uri('/api/forms/test/ods/custom-view', user=local_user))
2556
    zipf = zipfile.ZipFile(BytesIO(resp.body))
2557
    ods_sheet = ET.parse(zipf.open('content.xml'))
2558
    assert len(ods_sheet.findall('.//{%s}table-row' % ods.NS['table'])) == 21
2559

  
2560

  
2561
def test_api_geojson_formdata_custom_view(pub, local_user):
2562
    Role.wipe()
2563
    role = Role(name='test')
2564
    role.store()
2565

  
2566
    FormDef.wipe()
2567
    formdef = FormDef()
2568
    formdef.name = 'test'
2569
    formdef.workflow_roles = {'_receiver': role.id}
2570
    formdef.fields = [
2571
        fields.StringField(id='0', label='foobar', varname='foobar'),
2572
    ]
2573
    formdef.geolocations = {'base': 'Location'}
2574
    formdef.store()
2575

  
2576
    data_class = formdef.data_class()
2577
    data_class.wipe()
2578

  
2579
    for i in range(30):
2580
        formdata = data_class()
2581
        formdata.data = {'0': 'FOO BAR %d' % i}
2582
        formdata.geolocations = {'base': {'lat': 48, 'lon': 2}}
2583
        formdata.user_id = local_user.id
2584
        formdata.just_created()
2585
        if i % 3 == 0:
2586
            formdata.jump_status('new')
2587
        else:
2588
            formdata.jump_status('finished')
2589
        formdata.store()
2590

  
2591
    # add proper role to user
2592
    local_user.roles = [role.id]
2593
    local_user.store()
2594

  
2595
    # check it now gets the data
2596
    resp = get_app(pub).get(sign_uri('/api/forms/test/geojson', user=local_user))
2597
    assert len(resp.json['features']) == 10
2598

  
2599
    pub.custom_view_class.wipe()
2600
    custom_view = pub.custom_view_class()
2601
    custom_view.title = 'custom view'
2602
    custom_view.formdef = formdef
2603
    custom_view.columns = {'list': [{'id': '0'}]}
2604
    custom_view.filters = {"filter": "done", "filter-status": "on"}
2605
    custom_view.visibility = 'any'
2606
    custom_view.store()
2607

  
2608
    resp = get_app(pub).get(sign_uri('/api/forms/test/geojson/custom-view', user=local_user))
2609
    assert len(resp.json['features']) == 20
2610

  
2611

  
2612
def test_api_global_geojson(pub, local_user):
2613
    Role.wipe()
2614
    role = Role(name='test')
2615
    role.store()
2616

  
2617
    FormDef.wipe()
2618
    formdef = FormDef()
2619
    formdef.name = 'test'
2620
    formdef.workflow_roles = {'_receiver': role.id}
2621
    formdef.fields = []
2622
    formdef.store()
2623

  
2624
    data_class = formdef.data_class()
2625
    data_class.wipe()
2626

  
2627
    formdef.geolocations = {'base': 'Location'}
2628
    formdef.store()
2629

  
2630
    for i in range(30):
2631
        formdata = data_class()
2632
        date = time.strptime('2014-01-20', '%Y-%m-%d')
2633
        formdata.geolocations = {'base': {'lat': 48, 'lon': 2}}
2634
        formdata.user_id = local_user.id
2635
        formdata.just_created()
2636
        if i % 3 == 0:
2637
            formdata.jump_status('new')
2638
        else:
2639
            formdata.jump_status('finished')
2640
        formdata.store()
2641

  
2642
    if not pub.is_using_postgresql():
2643
        resp = get_app(pub).get(sign_uri('/api/forms/geojson', user=local_user), status=404)
2644
        pytest.skip('this requires SQL')
2645
        return
2646

  
2647
    # check empty content if user doesn't have the appropriate role
2648
    resp = get_app(pub).get(sign_uri('/api/forms/geojson', user=local_user))
2649
    assert 'features' in resp.json
2650
    assert len(resp.json['features']) == 0
2651

  
2652
    # add proper role to user
2653
    local_user.roles = [role.id]
2654
    local_user.store()
2655

  
2656
    # check it gets the data
2657
    resp = get_app(pub).get(sign_uri('/api/forms/geojson', user=local_user))
2658
    assert 'features' in resp.json
2659
    assert len(resp.json['features']) == 10
2660

  
2661
    # check with a filter
2662
    resp = get_app(pub).get(sign_uri('/api/forms/geojson?status=done', user=local_user))
2663
    assert 'features' in resp.json
2664
    assert len(resp.json['features']) == 20
2665

  
2666

  
2667
def test_api_global_listing(pub, local_user):
2668
    if not pub.is_using_postgresql():
2669
        resp = get_app(pub).get(sign_uri('/api/forms/geojson', user=local_user), status=404)
2670
        pytest.skip('this requires SQL')
2671
        return
2672

  
2673
    Role.wipe()
2674
    role = Role(name='test')
2675
    role.store()
2676

  
2677
    # check there's no crash if there are no formdefs
2678
    resp = get_app(pub).get(sign_uri('/api/forms/', user=local_user))
2679
    assert len(resp.json['data']) == 0
2680

  
2681
    FormDef.wipe()
2682
    formdef = FormDef()
2683
    formdef.name = 'test'
2684
    formdef.workflow_roles = {'_receiver': role.id}
2685
    formdef.fields = [
2686
        fields.StringField(id='0', label='foobar', varname='foobar'),
2687
    ]
2688
    formdef.store()
2689

  
2690
    data_class = formdef.data_class()
2691
    data_class.wipe()
2692

  
2693
    formdef.store()
2694

  
2695
    for i in range(30):
2696
        formdata = data_class()
2697
        date = time.strptime('2014-01-20', '%Y-%m-%d')
2698
        formdata.data = {'0': 'FOO BAR'}
2699
        formdata.user_id = local_user.id
2700
        formdata.just_created()
2701
        if i % 3 == 0:
2702
            formdata.jump_status('new')
2703
        else:
2704
            formdata.jump_status('finished')
2705
        formdata.store()
2706

  
2707
    # check empty content if user doesn't have the appropriate role
2708
    resp = get_app(pub).get(sign_uri('/api/forms/', user=local_user))
2709
    assert len(resp.json['data']) == 0
2710

  
2711
    # add proper role to user
2712
    local_user.roles = [role.id]
2713
    local_user.store()
2714

  
2715
    # check it gets the data
2716
    resp = get_app(pub).get(sign_uri('/api/forms/', user=local_user))
2717
    assert len(resp.json['data']) == 10
2718

  
2719
    # check with a filter
2720
    resp = get_app(pub).get(sign_uri('/api/forms/?status=done', user=local_user))
2721
    assert len(resp.json['data']) == 20
2722

  
2723
    # check limit/offset
2724
    resp = get_app(pub).get(sign_uri('/api/forms/?status=done&limit=5', user=local_user))
2725
    assert len(resp.json['data']) == 5
2726
    resp = get_app(pub).get(sign_uri('/api/forms/?status=done&offset=5&limit=5', user=local_user))
2727
    assert len(resp.json['data']) == 5
2728
    resp = get_app(pub).get(sign_uri('/api/forms/?status=done&offset=18&limit=5', user=local_user))
2729
    assert len(resp.json['data']) == 2
2730

  
2731
    # check error handling
2732
    get_app(pub).get(sign_uri('/api/forms/?status=done&limit=plop', user=local_user), status=400)
2733
    get_app(pub).get(sign_uri('/api/forms/?status=done&offset=plop', user=local_user), status=400)
2734
    get_app(pub).get(sign_uri('/api/forms/?category_id=plop', user=local_user), status=400)
2735

  
2736
    # check when there are missing statuses
2737
    for formdata in data_class.select():
2738
        formdata.status = 'wf-missing'
2739
        formdata.store()
2740
    resp = get_app(pub).get(sign_uri('/api/forms/?status=all', user=local_user))
2741
    assert resp.json['data'][0]['status'] is None
2742
    assert 'unknown' in resp.json['data'][0]['title']
2743

  
2744

  
2745
def test_api_global_listing_ignored_roles(pub, local_user):
2746
    test_api_global_listing(pub, local_user)
2747

  
2748
    role = Role(name='test2')
2749
    role.store()
2750

  
2751
    formdef = FormDef()
2752
    formdef.name = 'test2'
2753
    formdef.workflow_roles = {'_receiver': role.id}
2754
    formdef.fields = [
2755
        fields.StringField(id='0', label='foobar', varname='foobar'),
2756
    ]
2757
    formdef.store()
2758

  
2759
    data_class = formdef.data_class()
2760
    data_class.wipe()
2761

  
2762
    for i in range(10):
2763
        formdata = data_class()
2764
        date = time.strptime('2014-01-20', '%Y-%m-%d')
2765
        formdata.data = {'0': 'FOO BAR'}
2766
        formdata.user_id = local_user.id
2767
        formdata.just_created()
2768
        formdata.jump_status('new')
2769
        formdata.store()
2770

  
2771
    # considering roles
2772
    resp = get_app(pub).get(sign_uri('/api/forms/?status=all&limit=100', user=local_user))
2773
    assert len(resp.json['data']) == 30
2774

  
2775
    # ignore roles
2776
    resp = get_app(pub).get(sign_uri('/api/forms/?status=all&limit=100&ignore-roles=on', user=local_user))
2777
    assert len(resp.json['data']) == 40
2778

  
2779
    # check sensitive forms are not exposed
2780
    formdef.skip_from_360_view = True
2781
    formdef.store()
2782
    resp = get_app(pub).get(sign_uri('/api/forms/?status=all&limit=100&ignore-roles=on', user=local_user))
2783
    assert len(resp.json['data']) == 30
2784

  
2785

  
2786
def test_api_include_anonymised(pub, local_user):
2787
    if not pub.is_using_postgresql():
2788
        resp = get_app(pub).get(sign_uri('/api/forms/geojson', user=local_user), status=404)
2789
        pytest.skip('this requires SQL')
2790
        return
2791

  
2792
    Role.wipe()
2793
    role = Role(name='test')
2794
    role.store()
2795

  
2796
    # add proper role to user
2797
    local_user.roles = [role.id]
2798
    local_user.store()
2799

  
2800
    FormDef.wipe()
2801
    formdef = FormDef()
2802
    formdef.name = 'test'
2803
    formdef.workflow_roles = {'_receiver': role.id}
2804
    formdef.fields = [
2805
        fields.StringField(id='0', label='foobar', varname='foobar'),
2806
    ]
2807
    formdef.store()
2808

  
2809
    data_class = formdef.data_class()
2810
    data_class.wipe()
2811

  
2812
    for i in range(10):
2813
        formdata = data_class()
2814
        formdata.data = {'0': 'FOO BAR'}
2815
        formdata.user_id = local_user.id
2816
        formdata.just_created()
2817
        formdata.jump_status('new')
2818
        formdata.store()
3
import json
4
import os
2819 5

  
2820
    # anonymise the last one
2821
    formdata.anonymise()
6
import mock
7
import pytest
8
from django.utils.six import StringIO
9
from quixote import get_publisher
10
from utilities import clean_temporary_pub, create_temporary_pub, get_app
2822 11

  
2823
    resp = get_app(pub).get(sign_uri('/api/forms/', user=local_user))
2824
    assert len(resp.json['data']) == 10
12
from wcs.api_utils import sign_url
13
from wcs.formdef import FormDef
14
from wcs.qommon.http_request import HTTPRequest
2825 15

  
2826
    resp = get_app(pub).get(sign_uri('/api/forms/?include-anonymised=on', user=local_user))
2827
    assert len(resp.json['data']) == 10
2828 16

  
2829
    resp = get_app(pub).get(sign_uri('/api/forms/?include-anonymised=off', user=local_user))
2830
    assert len(resp.json['data']) == 9
17
def pytest_generate_tests(metafunc):
18
    if 'pub' in metafunc.fixturenames:
19
        metafunc.parametrize('pub', ['pickle', 'sql'], indirect=True)
2831 20

  
2832 21

  
2833 22
@pytest.fixture
2834
def ics_data(local_user):
2835
    Role.wipe()
2836
    role = Role(name='test')
2837
    role.store()
2838

  
2839
    FormDef.wipe()
2840
    formdef = FormDef()
2841
    formdef.url_name = 'test'
2842
    formdef.name = 'testé'
2843
    formdef.workflow_roles = {'_receiver': role.id}
2844
    formdef.fields = [
2845
        fields.StringField(id='0', label='foobar', varname='foobar'),
2846
        fields.StringField(id='1', label='foobar2', varname='foobar2'),
2847
    ]
2848
    formdef.digest_template = 'plöp {{ form_var_foobar }} plÔp'
2849
    formdef.store()
2850

  
2851
    data_class = formdef.data_class()
2852
    data_class.wipe()
2853

  
2854
    date = datetime.datetime(2014, 1, 20, 12, 00)
2855
    for i in range(30):
2856
        formdata = data_class()
2857
        formdata.data = {'0': (date + datetime.timedelta(days=i)).strftime('%Y-%m-%d %H:%M')}
2858
        formdata.data['1'] = (date + datetime.timedelta(days=i, minutes=i + 1)).strftime('%Y-%m-%d %H:%M')
2859
        formdata.user_id = local_user.id
2860
        formdata.just_created()
2861
        if i % 3 == 0:
2862
            formdata.jump_status('new')
2863
        else:
2864
            formdata.jump_status('finished')
2865
        formdata.store()
2866

  
2867
    # not a datetime: ignored
2868
    date = datetime.date(2014, 1, 20)
2869
    formdata = data_class()
2870
    formdata.data = {'0': '12:00'}
2871
    formdata.data['1'] = '13:00'
2872
    formdata.user_id = local_user.id
2873
    formdata.just_created()
2874
    formdata.jump_status('new')
2875
    formdata.store()
2876

  
2877

  
2878
def test_api_ics_formdata(pub, local_user, ics_data):
2879
    role = Role.select()[0]
2880

  
2881
    # check access is denied if the user has not the appropriate role
2882
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar', user=local_user), status=403)
2883
    # even if there's an anonymse parameter
2884
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar?anonymise', user=local_user), status=403)
2885

  
2886
    # add proper role to user
2887
    local_user.roles = [role.id]
2888
    local_user.store()
2889

  
2890
    def remove_dtstamp(body):
2891
        # remove dtstamp as the precise timing may vary between two consecutive
2892
        # calls and we shouldn't care.
2893
        return re.sub('DTSTAMP:.*', 'DTSTAMP:--', body)
2894

  
2895
    # check 404 on incomplete ics url access
2896
    assert get_app(pub).get(sign_uri('/api/forms/test/ics/', user=local_user), status=404)
2897

  
2898
    # check it gets the data
2899
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar', user=local_user))
2900
    resp2 = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar/', user=local_user))
2901
    assert remove_dtstamp(resp.text) == remove_dtstamp(resp2.text)
2902
    assert resp.headers['content-type'] == 'text/calendar; charset=utf-8'
2903
    assert resp.text.count('BEGIN:VEVENT') == 10
2904
    # check that description contains form name, display id, workflow status,
2905
    # backoffice url and attached user
2906
    pattern = re.compile(u'DESCRIPTION:testé \| 1-\d+ \| New', re.MULTILINE)
2907
    m = pattern.findall(resp.text)
2908
    assert len(m) == 10
2909
    assert resp.text.count('Jean Darmette') == 10
2910
    assert resp.text.count('DTSTART') == 10
2911

  
2912
    # check formdata digest summary and description contains the formdata digest
2913
    pattern = re.compile(r'SUMMARY:testé #1-\d+ - plöp \d{4}-\d{2}-\d{2} \d{2}:\d{2} plÔp', re.MULTILINE)
2914
    m = pattern.findall(resp.text)
2915
    assert len(m) == 10
2916
    assert resp.text.count(r'plöp') == 20
2917

  
2918
    # check with a filter
2919
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar?filter=done', user=local_user))
2920
    assert resp.text.count('BEGIN:VEVENT') == 20
2921
    pattern = re.compile(u'DESCRIPTION:testé \| 1-\d+ \| Finished', re.MULTILINE)
2922
    m = pattern.findall(resp.text)
2923
    assert len(m) == 20
2924
    assert resp.text.count('Jean Darmette') == 20
2925

  
2926
    # check 404 on erroneous field var
2927
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/xxx', user=local_user), status=404)
2928

  
2929
    # check 404 on an erroneous field var for endtime
2930
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar/xxx', user=local_user), status=404)
2931

  
2932
    # check 404 on too many path elements
2933
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar/foobar2/xxx', user=local_user), status=404)
2934

  
2935
    # check ics data with start and end varnames
2936
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar/foobar2', user=local_user))
2937
    resp2 = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar/foobar2/', user=local_user))
2938
    assert remove_dtstamp(resp.text) == remove_dtstamp(resp2.text)
2939
    assert resp.text.count('DTSTART') == 10
2940
    assert resp.text.count('DTEND') == 10
2941

  
2942

  
2943
def test_api_ics_formdata_http_auth(pub, local_user, admin_user, ics_data):
2944
    role = Role.select()[0]
2945

  
2946
    # check as admin
2947
    app = login(get_app(pub))
2948
    resp = app.get('/api/forms/test/ics/foobar', status=200)
2949

  
2950
    # no access
2951
    app = get_app(pub)
2952
    resp = app.get('/api/forms/test/ics/foobar?email=%s' % local_user.email, status=401)
2953
    assert resp.headers['Www-Authenticate']
2954

  
2955
    # auth but no access
2956
    app = get_app(pub)
2957
    app.authorization = ('Basic', ('user', 'password'))
2958
    resp = app.get('/api/forms/test/ics/foobar?email=%s' % local_user.email, status=401)
2959

  
2960
    # add authentication info
2961
    pub.load_site_options()
2962
    pub.site_options.add_section('api-http-auth-ics')
2963
    pub.site_options.set('api-http-auth-ics', 'user', 'password')
2964
    pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
2965

  
2966
    # check access is denied if the user has not the appropriate role
2967
    resp = app.get('/api/forms/test/ics/foobar?email=%s' % local_user.email, status=403)
2968

  
2969
    # check access is denied if the user is not specified
2970
    resp = app.get('/api/forms/test/ics/foobar', status=403)
2971

  
2972
    # add proper role to user
2973
    local_user.roles = [role.id]
2974
    local_user.store()
2975

  
2976
    # check it gets the data
2977
    resp = app.get('/api/forms/test/ics/foobar?email=%s' % local_user.email, status=200)
2978
    assert resp.headers['content-type'] == 'text/calendar; charset=utf-8'
2979
    assert resp.text.count('BEGIN:VEVENT') == 10
2980

  
2981
    # check it fails with a different password
2982
    app.authorization = ('Basic', ('user', 'password2'))
2983
    resp = app.get('/api/forms/test/ics/foobar?email=%s' % local_user.email, status=401)
2984

  
2985

  
2986
def test_api_ics_formdata_custom_view(pub, local_user, ics_data):
2987
    role = Role.select()[0]
2988

  
2989
    formdef = FormDef.get_by_urlname('test')
2990

  
2991
    pub.custom_view_class.wipe()
2992
    custom_view = pub.custom_view_class()
2993
    custom_view.title = 'custom view'
2994
    custom_view.formdef = formdef
2995
    custom_view.columns = {'list': [{'id': '0'}]}
2996
    custom_view.filters = {}
2997
    custom_view.visibility = 'any'
2998
    custom_view.store()
2999

  
3000
    # check access is denied if the user has not the appropriate role
3001
    resp = get_app(pub).get(sign_uri('/api/forms/test/custom-view/ics/foobar', user=local_user), status=403)
3002
    # even if there's an anonymise parameter
3003
    resp = get_app(pub).get(
3004
        sign_uri('/api/forms/test/custom-view/ics/foobar?anonymise', user=local_user), status=403
3005
    )
3006

  
3007
    # add proper role to user
3008
    local_user.roles = [role.id]
3009
    local_user.store()
3010

  
3011
    def remove_dtstamp(body):
3012
        # remove dtstamp as the precise timing may vary between two consecutive
3013
        # calls and we shouldn't care.
3014
        return re.sub('DTSTAMP:.*', 'DTSTAMP:--', body)
3015

  
3016
    # check it gets the data
3017
    resp = get_app(pub).get(sign_uri('/api/forms/test/custom-view/ics/foobar', user=local_user))
3018
    resp2 = get_app(pub).get(sign_uri('/api/forms/test/custom-view/ics/foobar/', user=local_user))
3019
    assert remove_dtstamp(resp.text) == remove_dtstamp(resp2.text)
3020
    assert resp.headers['content-type'] == 'text/calendar; charset=utf-8'
3021
    assert resp.text.count('BEGIN:VEVENT') == 10
3022

  
3023
    # check ics data with start and end varnames
3024
    resp = get_app(pub).get(sign_uri('/api/forms/test/custom-view/ics/foobar/foobar2', user=local_user))
3025
    resp2 = get_app(pub).get(sign_uri('/api/forms/test/custom-view/ics/foobar/foobar2/', user=local_user))
3026
    assert remove_dtstamp(resp.text) == remove_dtstamp(resp2.text)
3027
    assert resp.text.count('DTSTART') == 10
3028
    assert resp.text.count('DTEND') == 10
3029

  
3030

  
3031
def test_roles(pub, local_user):
3032
    Role.wipe()
3033
    role = Role(name='Hello World')
3034
    role.emails = ['toto@example.com', 'zozo@example.com']
3035
    role.details = 'kouign amann'
3036
    role.store()
3037

  
3038
    resp = get_app(pub).get('/api/roles', status=403)
3039

  
3040
    resp = get_app(pub).get(sign_uri('/api/roles'))
3041
    assert resp.json['data'][0]['text'] == 'Hello World'
3042
    assert resp.json['data'][0]['slug'] == 'hello-world'
3043
    assert resp.json['data'][0]['emails'] == ['toto@example.com', 'zozo@example.com']
3044
    assert resp.json['data'][0]['emails_to_members'] == False
3045
    assert resp.json['data'][0]['details'] == 'kouign amann'
3046

  
3047
    # also check old endpoint, for compatibility
3048
    resp = get_app(pub).get(sign_uri('/roles'), headers={'Accept': 'application/json'})
3049
    assert resp.json['data'][0]['text'] == 'Hello World'
3050
    assert resp.json['data'][0]['slug'] == 'hello-world'
3051
    assert resp.json['data'][0]['emails'] == ['toto@example.com', 'zozo@example.com']
3052
    assert resp.json['data'][0]['emails_to_members'] == False
3053
    assert resp.json['data'][0]['details'] == 'kouign amann'
3054

  
3055

  
3056
def test_users(pub, local_user):
3057
    resp = get_app(pub).get('/api/users/', status=403)
3058

  
3059
    resp = get_app(pub).get(sign_uri('/api/users/'))
3060
    assert resp.json['data'][0]['user_display_name'] == local_user.name
3061
    assert resp.json['data'][0]['user_email'] == local_user.email
3062
    assert resp.json['data'][0]['user_id'] == local_user.id
3063

  
3064
    role = Role(name='Foo bar')
3065
    role.store()
3066
    local_user.roles = [role.id]
3067
    local_user.store()
3068

  
3069
    resp = get_app(pub).get(sign_uri('/api/users/?q=jean'))
3070
    assert resp.json['data'][0]['user_email'] == local_user.email
3071
    assert len(resp.json['data'][0]['user_roles']) == 1
3072
    assert resp.json['data'][0]['user_roles'][0]['name'] == 'Foo bar'
3073

  
3074
    resp = get_app(pub).get(sign_uri('/api/users/?q=foobar'))
3075
    assert len(resp.json['data']) == 0
3076

  
3077
    from wcs.admin.settings import UserFieldsFormDef
3078

  
3079
    formdef = UserFieldsFormDef(pub)
3080
    formdef.fields.append(fields.StringField(id='3', label='test', type='string'))
3081
    formdef.store()
3082

  
3083
    local_user.form_data = {'3': 'HELLO'}
3084
    local_user.set_attributes_from_formdata(local_user.form_data)
3085
    local_user.store()
3086

  
3087
    resp = get_app(pub).get(sign_uri('/api/users/?q=HELLO'))
3088
    assert len(resp.json['data']) == 1
3089
    resp = get_app(pub).get(sign_uri('/api/users/?q=foobar'))
3090
    assert len(resp.json['data']) == 0
3091

  
3092

  
3093
def test_users_unaccent(pub, local_user):
3094
    local_user.name = 'Jean Sénisme'
3095
    local_user.store()
3096
    resp = get_app(pub).get(sign_uri('/api/users/?q=jean'))
3097
    assert resp.json['data'][0]['user_email'] == local_user.email
3098

  
3099
    resp = get_app(pub).get(sign_uri('/api/users/?q=senisme'))
3100
    assert resp.json['data'][0]['user_email'] == local_user.email
3101

  
3102
    resp = get_app(pub).get(sign_uri('/api/users/?q=sénisme'))
3103
    assert resp.json['data'][0]['user_email'] == local_user.email
3104

  
3105
    resp = get_app(pub).get(sign_uri('/api/users/?q=blah'))
3106
    assert len(resp.json['data']) == 0
3107

  
3108

  
3109
def test_workflow_trigger(pub, local_user):
3110
    workflow = Workflow(name='test')
3111
    st1 = workflow.add_status('Status1', 'st1')
3112
    jump = JumpWorkflowStatusItem()
3113
    jump.trigger = 'XXX'
3114
    jump.status = 'st2'
3115
    st1.items.append(jump)
3116
    jump.parent = st1
3117
    st2 = workflow.add_status('Status2', 'st2')
3118
    workflow.store()
3119

  
3120
    FormDef.wipe()
3121
    formdef = FormDef()
3122
    formdef.name = 'test'
3123
    formdef.fields = []
3124
    formdef.workflow_id = workflow.id
3125
    formdef.store()
3126

  
3127
    formdef.data_class().wipe()
3128
    formdata = formdef.data_class()()
3129
    formdata.just_created()
3130
    formdata.store()
3131
    assert formdef.data_class().get(formdata.id).status == 'wf-st1'
3132

  
3133
    resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=200)
3134
    assert formdef.data_class().get(formdata.id).status == 'wf-st2'
3135

  
3136
    # check with trailing slash
3137
    formdata.store()  # reset
3138
    resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX/'), status=200)
3139
    assert formdef.data_class().get(formdata.id).status == 'wf-st2'
3140

  
3141
    Role.wipe()
3142
    role = Role(name='xxx')
3143
    role.store()
3144

  
3145
    jump.by = [role.id]
3146
    workflow.store()
3147

  
3148
    formdata.store()  # (will get back to wf-st1)
3149
    resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=403)
3150

  
3151
    resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX', user=local_user), status=403)
3152

  
3153
    local_user.roles = [role.id]
3154
    local_user.store()
3155
    resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX', user=local_user), status=200)
3156

  
3157

  
3158
def test_workflow_trigger_with_data(pub, local_user):
3159
    workflow = Workflow(name='test')
3160
    st1 = workflow.add_status('Status1', 'st1')
3161
    jump = JumpWorkflowStatusItem()
3162
    jump.trigger = 'XXX'
3163
    jump.status = 'st2'
3164
    st1.items.append(jump)
3165
    jump.parent = st1
3166
    st2 = workflow.add_status('Status2', 'st2')
3167
    workflow.store()
3168

  
3169
    FormDef.wipe()
3170
    formdef = FormDef()
3171
    formdef.name = 'test'
3172
    formdef.fields = []
3173
    formdef.workflow_id = workflow.id
3174
    formdef.store()
3175

  
3176
    formdef.data_class().wipe()
3177
    formdata = formdef.data_class()()
3178
    formdata.just_created()
3179
    formdata.store()
3180

  
3181
    get_app(pub).post_json(
3182
        sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=200, params={'test': 'data'}
3183
    )
3184
    assert formdef.data_class().get(formdata.id).status == 'wf-st2'
3185
    assert formdef.data_class().get(formdata.id).workflow_data == {'test': 'data'}
3186

  
3187
    # post with empty dictionary
3188
    formdata.store()  # reset
3189
    get_app(pub).post_json(sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=200, params={})
3190
    assert formdef.data_class().get(formdata.id).status == 'wf-st2'
3191
    assert not formdef.data_class().get(formdata.id).workflow_data
3192

  
3193
    # post with empty data
3194
    formdata.store()  # reset
3195
    get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=200)
3196
    assert formdef.data_class().get(formdata.id).status == 'wf-st2'
3197
    assert not formdef.data_class().get(formdata.id).workflow_data
3198

  
3199
    # post with empty data, but declare json content-type
3200
    formdata.store()  # reset
3201
    get_app(pub).post(
3202
        sign_uri(formdata.get_url() + 'jump/trigger/XXX'),
3203
        status=200,
3204
        headers={'content-type': 'application/json'},
3205
    )
3206
    assert formdef.data_class().get(formdata.id).status == 'wf-st2'
3207
    assert not formdef.data_class().get(formdata.id).workflow_data
3208

  
3209
    # post with invalid JSON data
3210
    formdata.store()  # reset
3211
    get_app(pub).post(
3212
        sign_uri(formdata.get_url() + 'jump/trigger/XXX'),
3213
        status=400,
3214
        headers={'content-type': 'application/json'},
3215
        params='ERROR',
3216
    )
3217

  
3218

  
3219
def test_workflow_trigger_with_condition(pub, local_user):
3220
    workflow = Workflow(name='test')
3221
    st1 = workflow.add_status('Status1', 'st1')
3222
    jump = JumpWorkflowStatusItem()
3223
    jump.trigger = 'XXX'
3224
    jump.condition = {'type': 'django', 'value': 'form_var_foo == "bar"'}
3225
    jump.status = 'st2'
3226
    st1.items.append(jump)
3227
    jump.parent = st1
3228
    st2 = workflow.add_status('Status2', 'st2')
3229
    workflow.store()
3230

  
3231
    FormDef.wipe()
3232
    formdef = FormDef()
3233
    formdef.name = 'test'
3234
    formdef.fields = [fields.StringField(id='0', label='foo', varname='foo')]
3235
    formdef.workflow_id = workflow.id
3236
    formdef.store()
3237

  
3238
    formdef.data_class().wipe()
3239
    formdata = formdef.data_class()()
3240
    formdata.data = {'0': 'foo'}
3241
    formdata.just_created()
3242
    formdata.store()
3243
    assert formdef.data_class().get(formdata.id).status == 'wf-st1'
3244

  
3245
    resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=403)
3246
    assert resp.json == {'err_desc': 'unmet condition', 'err': 1}
3247
    assert formdef.data_class().get(formdata.id).status == 'wf-st1'
3248
    # check without json
3249
    resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX', format=None), status=403)
3250
    assert resp.content_type == 'text/html'
3251

  
3252
    formdata.data['0'] = 'bar'
3253
    formdata.store()
3254
    resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'))
3255
    assert resp.json == {'err': 0, 'url': None}
3256

  
3257

  
3258
def test_workflow_trigger_jump_once(pub, local_user):
3259
    workflow = Workflow(name='test')
3260
    st1 = workflow.add_status('Status1', 'st1')
3261
    st2 = workflow.add_status('Status2', 'st2')
3262
    st3 = workflow.add_status('Status3', 'st3')
3263
    jump = JumpWorkflowStatusItem()
3264
    jump.trigger = 'XXX'
3265
    jump.status = 'st2'
3266
    st1.items.append(jump)
3267
    jump.parent = st1
3268
    jump = JumpWorkflowStatusItem()
3269
    jump.trigger = 'XXX'
3270
    jump.status = 'st3'
3271
    st2.items.append(jump)
3272
    jump.parent = st2
3273
    workflow.store()
3274

  
3275
    FormDef.wipe()
3276
    formdef = FormDef()
3277
    formdef.name = 'test'
3278
    formdef.fields = []
3279
    formdef.workflow_id = workflow.id
3280
    formdef.store()
3281

  
3282
    formdef.data_class().wipe()
3283
    formdata = formdef.data_class()()
3284
    formdata.just_created()
3285
    formdata.store()
3286
    assert formdef.data_class().get(formdata.id).status == 'wf-st1'
3287

  
3288
    resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'))
3289
    assert resp.json == {'err': 0, 'url': None}
3290
    assert formdef.data_class().get(formdata.id).status == 'wf-st2'
3291

  
3292
    resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'))
3293
    assert resp.json == {'err': 0, 'url': None}
3294
    assert formdef.data_class().get(formdata.id).status == 'wf-st3'
3295

  
3296

  
3297
def test_workflow_global_webservice_trigger(pub, local_user):
3298
    workflow = Workflow(name='test')
3299
    st1 = workflow.add_status('Status1', 'st1')
3300

  
3301
    ac1 = workflow.add_global_action('Action', 'ac1')
3302
    trigger = ac1.append_trigger('webservice')
3303
    trigger.identifier = 'plop'
3304

  
3305
    add_to_journal = RegisterCommenterWorkflowStatusItem()
3306
    add_to_journal.id = '_add_to_journal'
3307
    add_to_journal.comment = 'HELLO WORLD'
3308
    ac1.items.append(add_to_journal)
3309
    add_to_journal.parent = ac1
3310

  
3311
    workflow.store()
3312

  
3313
    FormDef.wipe()
3314
    formdef = FormDef()
3315
    formdef.name = 'test'
3316
    formdef.fields = []
3317
    formdef.workflow_id = workflow.id
3318
    formdef.store()
3319

  
3320
    formdef.data_class().wipe()
3321
    formdata = formdef.data_class()()
3322
    formdata.just_created()
3323
    formdata.store()
3324
    assert formdef.data_class().get(formdata.id).status == 'wf-st1'
3325

  
3326
    # call to undefined hook
3327
    resp = get_app(pub).post(sign_uri(formdata.get_url() + 'hooks/XXX/'), status=404)
3328
    resp = get_app(pub).post(sign_uri(formdata.get_api_url() + 'hooks/XXX/'), status=404)
3329

  
3330
    # anonymous call
3331
    resp = get_app(pub).post(formdata.get_url() + 'hooks/plop/', status=200)
3332
    assert formdef.data_class().get(formdata.id).evolution[-1].parts[-1].content == 'HELLO WORLD'
3333

  
3334
    add_to_journal.comment = 'HELLO WORLD 2'
3335
    workflow.store()
3336
    resp = get_app(pub).post(formdata.get_api_url() + 'hooks/plop/', status=200)
3337
    assert formdef.data_class().get(formdata.id).evolution[-1].parts[-1].content == 'HELLO WORLD 2'
3338

  
3339
    # call requiring user
3340
    add_to_journal.comment = 'HELLO WORLD 3'
3341
    trigger.roles = ['logged-users']
3342
    workflow.store()
3343
    resp = get_app(pub).post(formdata.get_api_url() + 'hooks/plop/', status=403)
3344
    resp = get_app(pub).post(sign_uri(formdata.get_api_url() + 'hooks/plop/'), status=200)
3345
    assert formdef.data_class().get(formdata.id).evolution[-1].parts[-1].content == 'HELLO WORLD 3'
3346

  
3347
    # call requiring roles
3348
    add_to_journal.comment = 'HELLO WORLD 4'
3349
    trigger.roles = ['logged-users']
3350
    workflow.store()
3351
    Role.wipe()
3352
    role = Role(name='xxx')
3353
    role.store()
3354
    trigger.roles = [role.id]
3355
    workflow.store()
3356
    resp = get_app(pub).post(sign_uri(formdata.get_api_url() + 'hooks/plop/'), status=403)
3357
    resp = get_app(pub).post(sign_uri(formdata.get_api_url() + 'hooks/plop/', user=local_user), status=403)
3358

  
3359
    local_user.roles = [role.id]
3360
    local_user.store()
3361
    resp = get_app(pub).post(sign_uri(formdata.get_api_url() + 'hooks/plop/', user=local_user), status=200)
3362
    assert formdef.data_class().get(formdata.id).evolution[-1].parts[-1].content == 'HELLO WORLD 4'
23
def pub(request, emails):
24
    pub = create_temporary_pub(sql_mode=(request.param == 'sql'))
3363 25

  
3364
    # call adding data
3365
    add_to_journal.comment = 'HELLO {{plop_test}}'
3366
    workflow.store()
3367
    resp = get_app(pub).post_json(
3368
        sign_uri(formdata.get_api_url() + 'hooks/plop/', user=local_user), {'test': 'foobar'}, status=200
3369
    )
3370
    # (django templating make it turn into HTML)
3371
    assert formdef.data_class().get(formdata.id).evolution[-1].parts[-1].content == '<div>HELLO foobar</div>'
26
    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
27
    pub.set_app_dir(req)
28
    pub.cfg['identification'] = {'methods': ['password']}
29
    pub.cfg['language'] = {'language': 'en'}
30
    pub.write_cfg()
3372 31

  
3373
    # call adding data but with no actions
3374
    ac1.items = []
3375
    workflow.store()
3376
    resp = get_app(pub).post_json(
3377
        sign_uri(formdata.get_api_url() + 'hooks/plop/', user=local_user), {'test': 'BAR'}, status=200
32
    open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w').write(
33
        '''\
34
[api-secrets]
35
coucou = 1234
36
'''
3378 37
    )
3379
    assert formdef.data_class().get(formdata.id).workflow_data == {'plop': {'test': 'BAR'}}
3380

  
3381 38

  
3382
def test_workflow_global_webservice_trigger_no_trailing_slash(pub, local_user):
3383
    workflow = Workflow(name='test')
3384
    st1 = workflow.add_status('Status1', 'st1')
3385

  
3386
    ac1 = workflow.add_global_action('Action', 'ac1')
3387
    trigger = ac1.append_trigger('webservice')
3388
    trigger.identifier = 'plop'
3389

  
3390
    add_to_journal = RegisterCommenterWorkflowStatusItem()
3391
    add_to_journal.id = '_add_to_journal'
3392
    add_to_journal.comment = 'HELLO WORLD'
3393
    ac1.items.append(add_to_journal)
3394
    add_to_journal.parent = ac1
3395

  
3396
    workflow.store()
3397

  
3398
    FormDef.wipe()
3399
    formdef = FormDef()
3400
    formdef.name = 'test'
3401
    formdef.fields = []
3402
    formdef.workflow_id = workflow.id
3403
    formdef.store()
3404

  
3405
    formdef.data_class().wipe()
3406
    formdata = formdef.data_class()()
3407
    formdata.just_created()
3408
    formdata.store()
3409
    assert formdef.data_class().get(formdata.id).status == 'wf-st1'
39
    return pub
3410 40

  
3411
    # call to undefined hook
3412
    resp = get_app(pub).post(sign_uri(formdata.get_url() + 'hooks/XXX'), status=404)
3413
    resp = get_app(pub).post(sign_uri(formdata.get_api_url() + 'hooks/XXX'), status=404)
3414 41

  
3415
    # anonymous call
3416
    resp = get_app(pub).post(formdata.get_url() + 'hooks/plop', status=200)
3417
    assert formdef.data_class().get(formdata.id).evolution[-1].parts[-1].content == 'HELLO WORLD'
42
def teardown_module(module):
43
    clean_temporary_pub()
3418 44

  
3419 45

  
3420 46
def test_tracking_code(pub):
......
3502 128
    assert resp.json['msg'] == 'unknown condition type'
3503 129

  
3504 130

  
3505
@pytest.fixture(params=['sql', 'pickle'])
3506
def no_request_pub(request):
3507
    pub = create_temporary_pub(sql_mode=bool(request.param == 'sql'))
3508
    pub.app_dir = os.path.join(pub.APP_DIR, 'example.net')
3509
    pub.set_config()
3510

  
3511
    open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w').write(
3512
        '''
3513
[wscall-secrets]
3514
api.example.com = 1234
3515
'''
3516
    )
3517
    return pub
3518

  
3519

  
3520
def test_get_secret_and_orig(no_request_pub):
3521
    secret, orig = get_secret_and_orig('https://api.example.com/endpoint/')
3522
    assert secret == '1234'
3523
    assert orig == 'example.net'
3524

  
3525

  
3526 131
def test_reverse_geocoding(pub):
3527 132
    with mock.patch('wcs.qommon.misc.urlopen') as urlopen:
3528 133
        urlopen.side_effect = lambda *args: StringIO(json.dumps({'address': 'xxx'}))
......
3563 168
        )
3564 169

  
3565 170

  
3566
def test_formdef_submit_structured(pub, local_user):
3567
    Role.wipe()
3568
    role = Role(name='test')
3569
    role.store()
3570
    local_user.roles = [role.id]
3571
    local_user.store()
3572

  
3573
    FormDef.wipe()
3574
    formdef = FormDef()
3575
    formdef.name = 'test'
3576
    formdef.fields = [
3577
        fields.ItemField(
3578
            id='0',
3579
            label='foobar',
3580
            varname='foobar',
3581
            data_source={
3582
                'type': 'json',
3583
                'value': 'http://datasource.com',
3584
            },
3585
        ),
3586
        fields.ItemField(
3587
            id='1',
3588
            label='foobar1',
3589
            varname='foobar1',
3590
            data_source={
3591
                'type': 'formula',
3592
                'value': '[dict(id=i, text=\'label %s\' % i, foo=i) for i in range(10)]',
3593
            },
3594
        ),
3595
    ]
3596
    formdef.store()
3597
    data_class = formdef.data_class()
3598

  
3599
    def url():
3600
        signed_url = sign_url(
3601
            'http://example.net/api/formdefs/test/submit'
3602
            '?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
3603
            '1234',
3604
        )
3605
        return signed_url[len('http://example.net') :]
3606

  
3607
    with mock.patch('wcs.qommon.misc.urlopen') as urlopen:
3608
        urlopen.side_effect = lambda *args: StringIO(
3609
            '''\
3610
{"data": [{"id": 0, "text": "zéro", "foo": "bar"}, \
3611
{"id": 1, "text": "uné", "foo": "bar1"}, \
3612
{"id": 2, "text": "deux", "foo": "bar2"}]}'''
3613
        )
3614
        resp = get_app(pub).post_json(
3615
            url(),
3616
            {
3617
                'data': {
3618
                    '0': '0',
3619
                    "1": '3',
3620
                }
3621
            },
3622
        )
3623

  
3624
    formdata = data_class.get(resp.json['data']['id'])
3625
    assert formdata.status == 'wf-new'
3626
    assert formdata.data['0'] == '0'
3627
    assert formdata.data['0_display'] == 'zéro'
3628
    assert formdata.data['0_structured'] == {
3629
        'id': 0,
3630
        'text': 'zéro',
3631
        'foo': 'bar',
3632
    }
3633
    assert formdata.data['1'] == '3'
3634
    assert formdata.data['1_display'] == 'label 3'
3635
    assert formdata.data['1_structured'] == {
3636
        'id': 3,
3637
        'text': 'label 3',
3638
        'foo': 3,
3639
    }
3640

  
3641
    data_class.wipe()
3642

  
3643

  
3644 171
def test_geocoding(pub):
3645 172
    with mock.patch('wcs.qommon.misc.urlopen') as urlopen:
3646 173
        urlopen.side_effect = lambda *args: StringIO(json.dumps([{'lat': 0, 'lon': 0}]))
......
3689 216
            urlopen.call_args[0][0]
3690 217
            == 'http://reverse.example.net/?param=value&format=json&q=test&accept-language=en'
3691 218
        )
3692

  
3693

  
3694
def test_cards(pub, local_user):
3695
    Role.wipe()
3696
    role = Role(name='test')
3697
    role.store()
3698
    local_user.roles = [role.id]
3699
    local_user.store()
3700

  
3701
    CardDefCategory.wipe()
3702
    category = CardDefCategory()
3703
    category.name = 'Category A'
3704
    category.store()
3705

  
3706
    CardDef.wipe()
3707
    carddef = CardDef()
3708
    carddef.name = 'test'
3709
    carddef.fields = [fields.StringField(id='0', label='foobar', varname='foo')]
3710
    carddef.workflow_roles = {'_viewer': role.id}
3711
    carddef.digest_template = 'bla {{ form_var_foo }} xxx'
3712
    carddef.store()
3713

  
3714
    carddef.data_class().wipe()
3715
    formdata = carddef.data_class()()
3716
    formdata.data = {'0': 'blah'}
3717
    formdata.just_created()
3718
    formdata.store()
3719

  
3720
    custom_view = pub.custom_view_class()
3721
    custom_view.title = 'shared carddef custom view'
3722
    custom_view.formdef = carddef
3723
    custom_view.columns = {'list': [{'id': '0'}]}
3724
    custom_view.filters = {}
3725
    custom_view.visibility = 'any'
3726
    custom_view.store()
3727

  
3728
    custom_view = pub.custom_view_class()
3729
    custom_view.title = 'private carddef custom view'
3730
    custom_view.formdef = carddef
3731
    custom_view.columns = {'list': [{'id': '0'}]}
3732
    custom_view.filters = {}
3733
    custom_view.visibility = 'owner'
3734
    custom_view.user = local_user
3735
    custom_view.store()
3736

  
3737
    custom_view = pub.custom_view_class()
3738
    custom_view.title = 'datasource carddef custom view'
3739
    custom_view.formdef = carddef
3740
    custom_view.columns = {'list': [{'id': '0'}]}
3741
    custom_view.filters = {}
3742
    custom_view.visibility = 'datasource'
3743
    custom_view.store()
3744

  
3745
    resp = get_app(pub).get('/api/cards/@list', status=403)
3746
    resp = get_app(pub).get(sign_uri('/api/cards/@list'))
3747
    assert len(resp.json['data']) == 1
3748
    assert resp.json['data'][0]['slug'] == 'test'
3749
    assert resp.json['data'][0]['category_slug'] is None
3750
    assert resp.json['data'][0]['category_name'] is None
3751
    assert resp.json['data'][0]['custom_views'] == [
3752
        {'id': 'datasource-carddef-custom-view', 'text': 'datasource carddef custom view'},
3753
        {'id': 'shared-carddef-custom-view', 'text': 'shared carddef custom view'},
3754
    ]
3755

  
3756
    carddef.category = category
3757
    carddef.store()
3758
    resp = get_app(pub).get(sign_uri('/api/cards/@list'))
3759
    assert len(resp.json['data']) == 1
3760
    assert resp.json['data'][0]['slug'] == 'test'
3761
    assert resp.json['data'][0]['category_slug'] == 'category-a'
3762
    assert resp.json['data'][0]['category_name'] == 'Category A'
3763

  
3764
    # signed but anonymous
3765
    resp = get_app(pub).get(sign_uri('/api/cards/test/list?NameID='), status=403)
3766

  
3767
    # signed without specifying any user -> get everything
3768
    resp = get_app(pub).get(sign_uri('/api/cards/test/list'))
3769
    assert len(resp.json['data']) == 1
3770

  
3771
    resp = get_app(pub).get(sign_uri('/api/cards/test/list?NameID=%s' % local_user.name_identifiers[0]))
3772
    assert len(resp.json['data']) == 1
3773
    assert resp.json['data'][0]['display_id'] == formdata.get_display_id()
3774
    assert resp.json['data'][0]['display_name'] == formdata.get_display_name()
3775
    assert resp.json['data'][0]['digest'] == formdata.digest
3776
    assert resp.json['data'][0]['text'] == formdata.digest
3777
    resp = get_app(pub).get(
3778
        sign_uri('/api/cards/test/list?NameID=%s&full=on' % local_user.name_identifiers[0])
3779
    )
3780
    assert resp.json['data'][0]['fields']['foo'] == 'blah'
3781
    assert resp.json['data'][0]['digest'] == formdata.digest
3782
    assert resp.json['data'][0]['text'] == formdata.digest
3783

  
3784
    # get schema
3785
    resp = get_app(pub).get(sign_uri('/api/cards/test/@schema'), status=200)
3786
    assert len(resp.json['fields']) == 1
3787
    assert resp.json['fields'][0]['label'] == 'foobar'
3788
    assert resp.json['fields'][0]['varname'] == 'foo'
3789

  
3790

  
3791
def test_cards_import_csv(pub, local_user):
3792
    Role.wipe()
3793
    role = Role(name='test')
3794
    role.store()
3795
    local_user.roles = [role.id]
3796
    local_user.store()
3797

  
3798
    CardDef.wipe()
3799
    carddef = CardDef()
3800
    carddef.name = 'test'
3801
    carddef.fields = [
3802
        fields.StringField(id='0', label='foobar', varname='foo'),
3803
        fields.StringField(id='1', label='foobar2', varname='foo2'),
3804
    ]
3805
    carddef.workflow_roles = {'_viewer': role.id}
3806
    carddef.backoffice_submission_roles = [role.id]
3807
    carddef.digest_template = 'bla {{ form_var_foo }} xxx'
3808
    carddef.store()
3809

  
3810
    carddef.data_class().wipe()
3811

  
3812
    get_app(pub).get(sign_uri('/api/cards/test/import-csv'), status=405)
3813
    get_app(pub).put(sign_uri('/api/cards/test/import-csv'), status=403)
3814
    get_app(pub).put(
3815
        sign_uri('/api/cards/test/import-csv', user=local_user),
3816
        params=b'foobar;foobar2\nfirst entry;plop\nsecond entry;plop\n',
3817
        headers={'content-type': 'text/csv'},
3818
    )
3819
    assert carddef.data_class().count() == 2
3820
    assert set([x.data['0'] for x in carddef.data_class().select()]) == {'first entry', 'second entry'}
3821

  
3822

  
3823
def test_api_invalid_http_basic_auth(pub, local_user, admin_user, ics_data):
3824
    app = get_app(pub)
3825
    app.get(
3826
        '/api/forms/test/ics/foobar?email=%s' % local_user.email,
3827
        headers={'Authorization': 'Basic garbage'},
3828
        status=401,
3829
    )
tests/api/test_carddef.py
1
# -*- coding: utf-8 -*-
2

  
3
import os
4

  
5
import pytest
6
from quixote import get_publisher
7
from utilities import clean_temporary_pub, create_temporary_pub, get_app
8

  
9
from wcs import fields
10
from wcs.carddef import CardDef
11
from wcs.categories import CardDefCategory
12
from wcs.qommon.http_request import HTTPRequest
13
from wcs.roles import Role
14

  
15
from .utils import sign_uri
16

  
17

  
18
def pytest_generate_tests(metafunc):
19
    if 'pub' in metafunc.fixturenames:
20
        metafunc.parametrize('pub', ['pickle', 'sql'], indirect=True)
21

  
22

  
23
@pytest.fixture
24
def pub(request, emails):
25
    pub = create_temporary_pub(sql_mode=(request.param == 'sql'))
26

  
27
    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
28
    pub.set_app_dir(req)
29
    pub.cfg['identification'] = {'methods': ['password']}
30
    pub.cfg['language'] = {'language': 'en'}
31
    pub.write_cfg()
32

  
33
    open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w').write(
34
        '''\
35
[api-secrets]
36
coucou = 1234
37
'''
38
    )
39

  
40
    return pub
41

  
42

  
43
def teardown_module(module):
44
    clean_temporary_pub()
45

  
46

  
47
@pytest.fixture
48
def local_user():
49
    get_publisher().user_class.wipe()
50
    user = get_publisher().user_class()
51
    user.name = 'Jean Darmette'
52
    user.email = 'jean.darmette@triffouilis.fr'
53
    user.name_identifiers = ['0123456789']
54
    user.store()
55
    return user
56

  
57

  
58
def test_cards(pub, local_user):
59
    Role.wipe()
60
    role = Role(name='test')
61
    role.store()
62
    local_user.roles = [role.id]
63
    local_user.store()
64

  
65
    CardDefCategory.wipe()
66
    category = CardDefCategory()
67
    category.name = 'Category A'
68
    category.store()
69

  
70
    CardDef.wipe()
71
    carddef = CardDef()
72
    carddef.name = 'test'
73
    carddef.fields = [fields.StringField(id='0', label='foobar', varname='foo')]
74
    carddef.workflow_roles = {'_viewer': role.id}
75
    carddef.digest_template = 'bla {{ form_var_foo }} xxx'
76
    carddef.store()
77

  
78
    carddef.data_class().wipe()
79
    formdata = carddef.data_class()()
80
    formdata.data = {'0': 'blah'}
81
    formdata.just_created()
82
    formdata.store()
83

  
84
    custom_view = pub.custom_view_class()
85
    custom_view.title = 'shared carddef custom view'
86
    custom_view.formdef = carddef
87
    custom_view.columns = {'list': [{'id': '0'}]}
88
    custom_view.filters = {}
89
    custom_view.visibility = 'any'
90
    custom_view.store()
91

  
92
    custom_view = pub.custom_view_class()
93
    custom_view.title = 'private carddef custom view'
94
    custom_view.formdef = carddef
95
    custom_view.columns = {'list': [{'id': '0'}]}
96
    custom_view.filters = {}
97
    custom_view.visibility = 'owner'
98
    custom_view.user = local_user
99
    custom_view.store()
100

  
101
    custom_view = pub.custom_view_class()
102
    custom_view.title = 'datasource carddef custom view'
103
    custom_view.formdef = carddef
104
    custom_view.columns = {'list': [{'id': '0'}]}
105
    custom_view.filters = {}
106
    custom_view.visibility = 'datasource'
107
    custom_view.store()
108

  
109
    resp = get_app(pub).get('/api/cards/@list', status=403)
110
    resp = get_app(pub).get(sign_uri('/api/cards/@list'))
111
    assert len(resp.json['data']) == 1
112
    assert resp.json['data'][0]['slug'] == 'test'
113
    assert resp.json['data'][0]['category_slug'] is None
114
    assert resp.json['data'][0]['category_name'] is None
115
    assert resp.json['data'][0]['custom_views'] == [
116
        {'id': 'datasource-carddef-custom-view', 'text': 'datasource carddef custom view'},
117
        {'id': 'shared-carddef-custom-view', 'text': 'shared carddef custom view'},
118
    ]
119

  
120
    carddef.category = category
121
    carddef.store()
122
    resp = get_app(pub).get(sign_uri('/api/cards/@list'))
123
    assert len(resp.json['data']) == 1
124
    assert resp.json['data'][0]['slug'] == 'test'
125
    assert resp.json['data'][0]['category_slug'] == 'category-a'
126
    assert resp.json['data'][0]['category_name'] == 'Category A'
127

  
128
    # signed but anonymous
129
    resp = get_app(pub).get(sign_uri('/api/cards/test/list?NameID='), status=403)
130

  
131
    # signed without specifying any user -> get everything
132
    resp = get_app(pub).get(sign_uri('/api/cards/test/list'))
133
    assert len(resp.json['data']) == 1
134

  
135
    resp = get_app(pub).get(sign_uri('/api/cards/test/list?NameID=%s' % local_user.name_identifiers[0]))
136
    assert len(resp.json['data']) == 1
137
    assert resp.json['data'][0]['display_id'] == formdata.get_display_id()
138
    assert resp.json['data'][0]['display_name'] == formdata.get_display_name()
139
    assert resp.json['data'][0]['digest'] == formdata.digest
140
    assert resp.json['data'][0]['text'] == formdata.digest
141
    resp = get_app(pub).get(
142
        sign_uri('/api/cards/test/list?NameID=%s&full=on' % local_user.name_identifiers[0])
143
    )
144
    assert resp.json['data'][0]['fields']['foo'] == 'blah'
145
    assert resp.json['data'][0]['digest'] == formdata.digest
146
    assert resp.json['data'][0]['text'] == formdata.digest
147

  
148
    # get schema
149
    resp = get_app(pub).get(sign_uri('/api/cards/test/@schema'), status=200)
150
    assert len(resp.json['fields']) == 1
151
    assert resp.json['fields'][0]['label'] == 'foobar'
152
    assert resp.json['fields'][0]['varname'] == 'foo'
153

  
154

  
155
def test_cards_import_csv(pub, local_user):
156
    Role.wipe()
157
    role = Role(name='test')
158
    role.store()
159
    local_user.roles = [role.id]
160
    local_user.store()
161

  
162
    CardDef.wipe()
163
    carddef = CardDef()
164
    carddef.name = 'test'
165
    carddef.fields = [
166
        fields.StringField(id='0', label='foobar', varname='foo'),
167
        fields.StringField(id='1', label='foobar2', varname='foo2'),
168
    ]
169
    carddef.workflow_roles = {'_viewer': role.id}
170
    carddef.backoffice_submission_roles = [role.id]
171
    carddef.digest_template = 'bla {{ form_var_foo }} xxx'
172
    carddef.store()
173

  
174
    carddef.data_class().wipe()
175

  
176
    get_app(pub).get(sign_uri('/api/cards/test/import-csv'), status=405)
177
    get_app(pub).put(sign_uri('/api/cards/test/import-csv'), status=403)
178
    get_app(pub).put(
179
        sign_uri('/api/cards/test/import-csv', user=local_user),
180
        params=b'foobar;foobar2\nfirst entry;plop\nsecond entry;plop\n',
181
        headers={'content-type': 'text/csv'},
182
    )
183
    assert carddef.data_class().count() == 2
184
    assert set([x.data['0'] for x in carddef.data_class().select()]) == {'first entry', 'second entry'}
tests/api/test_category.py
1
# -*- coding: utf-8 -*-
2

  
3
import os
4

  
5
import pytest
6
from quixote import get_publisher
7
from utilities import clean_temporary_pub, create_temporary_pub, get_app
8

  
9
from wcs.categories import Category
10
from wcs.formdef import FormDef
11
from wcs.qommon.http_request import HTTPRequest
12
from wcs.roles import Role
13

  
14
from .utils import sign_uri
15

  
16

  
17
def pytest_generate_tests(metafunc):
18
    if 'pub' in metafunc.fixturenames:
19
        metafunc.parametrize('pub', ['pickle', 'sql'], indirect=True)
20

  
21

  
22
@pytest.fixture
23
def pub(request, emails):
24
    pub = create_temporary_pub(sql_mode=(request.param == 'sql'))
25

  
26
    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
27
    pub.set_app_dir(req)
28
    pub.cfg['identification'] = {'methods': ['password']}
29
    pub.cfg['language'] = {'language': 'en'}
30
    pub.write_cfg()
31

  
32
    open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w').write(
33
        '''\
34
[api-secrets]
35
coucou = 1234
36
'''
37
    )
38

  
39
    return pub
40

  
41

  
42
def teardown_module(module):
43
    clean_temporary_pub()
44

  
45

  
46
@pytest.fixture
47
def local_user():
48
    get_publisher().user_class.wipe()
49
    user = get_publisher().user_class()
50
    user.name = 'Jean Darmette'
51
    user.email = 'jean.darmette@triffouilis.fr'
52
    user.name_identifiers = ['0123456789']
53
    user.store()
54
    return user
55

  
56

  
57
def test_categories(pub):
58
    FormDef.wipe()
59
    Category.wipe()
60
    category = Category()
61
    category.name = 'Category'
62
    category.description = 'hello world'
63
    category.store()
64

  
65
    resp = get_app(pub).get('/api/categories/', headers={'Accept': 'application/json'})
66
    assert resp.json['data'] == []  # no advertised forms
67

  
68
    formdef = FormDef()
69
    formdef.name = 'test'
70
    formdef.category_id = category.id
71
    formdef.fields = []
72
    formdef.keywords = 'mobile, test'
73
    formdef.store()
74
    formdef.data_class().wipe()
75

  
76
    formdef = FormDef()
77
    formdef.name = 'test 2'
78
    formdef.category_id = category.id
79
    formdef.fields = []
80
    formdef.keywords = 'foobar'
81
    formdef.store()
82
    formdef.data_class().wipe()
83

  
84
    resp = get_app(pub).get('/api/categories/')
85
    resp2 = get_app(pub).get('/categories', headers={'Accept': 'application/json'})
86
    assert resp.json == resp2.json
87
    assert resp.json['data'][0]['title'] == 'Category'
88
    assert resp.json['data'][0]['url'] == 'http://example.net/category/'
89
    assert resp.json['data'][0]['description'] == '<p>hello world</p>'
90
    assert set(resp.json['data'][0]['keywords']) == set(['foobar', 'mobile', 'test'])
91
    assert 'forms' not in resp.json['data'][0]
92

  
93
    # check HTML description
94
    category.description = '<p><strong>hello world</strong></p>'
95
    category.store()
96
    resp = get_app(pub).get('/api/categories/')
97
    assert resp.json['data'][0]['description'] == category.description
98

  
99

  
100
def test_categories_private(pub, local_user):
101
    FormDef.wipe()
102
    Category.wipe()
103
    category = Category()
104
    category.name = 'Category'
105
    category.description = 'hello world'
106
    category.store()
107

  
108
    formdef = FormDef()
109
    formdef.name = 'test'
110
    formdef.category_id = category.id
111
    formdef.fields = []
112
    formdef.store()
113
    formdef.data_class().wipe()
114

  
115
    # open form
116
    resp = get_app(pub).get('/api/categories/')
117
    assert len(resp.json['data']) == 1
118

  
119
    # private form, the category doesn't appear anymore
120
    formdef.roles = ['plop']
121
    formdef.store()
122
    resp = get_app(pub).get('/api/categories/')
123
    assert len(resp.json['data']) == 0
124

  
125
    # not even for a signed request specifying an user
126
    resp = get_app(pub).get(sign_uri('http://example.net/api/categories/', local_user))
127
    assert len(resp.json['data']) == 0
128

  
129
    # but it appears if this is a signed request without user
130
    resp = get_app(pub).get(sign_uri('http://example.net/api/categories/'))
131
    assert len(resp.json['data']) == 1
132

  
133
    # or signed with an authorised user
134
    local_user.roles = ['plop']
135
    local_user.store()
136
    resp = get_app(pub).get(sign_uri('http://example.net/api/categories/', local_user))
137
    assert len(resp.json['data']) == 1
138

  
139

  
140
def test_categories_formdefs(pub, local_user):
141
    FormDef.wipe()
142
    Category.wipe()
143
    category = Category()
144
    category.name = 'Category'
145
    category.description = 'hello world'
146
    category.store()
147

  
148
    formdef = FormDef()
149
    formdef.name = 'test'
150
    formdef.category_id = category.id
151
    formdef.fields = []
152
    formdef.keywords = 'mobile, test'
153
    formdef.store()
154
    formdef.data_class().wipe()
155

  
156
    formdef = FormDef()
157
    formdef.name = 'test 2'
158
    formdef.category_id = category.id
159
    formdef.fields = []
160
    formdef.keywords = 'foobar'
161
    formdef.store()
162
    formdef.data_class().wipe()
163

  
164
    formdef2 = FormDef()
165
    formdef2.name = 'other test'
166
    formdef2.category_id = None
167
    formdef2.fields = []
168
    formdef2.store()
169
    formdef2.data_class().wipe()
170

  
171
    formdef2 = FormDef()
172
    formdef2.name = 'test disabled'
173
    formdef2.category_id = category.id
174
    formdef2.fields = []
175
    formdef2.disabled = True
176
    formdef2.store()
177
    formdef2.data_class().wipe()
178

  
179
    resp = get_app(pub).get('/api/categories/category/formdefs/', status=403)
180
    resp2 = get_app(pub).get('/category/json', status=403)
181
    resp = get_app(pub).get(sign_uri('/api/categories/category/formdefs/'))
182
    resp2 = get_app(pub).get(sign_uri('/category/json'))
183
    assert resp.json == resp2.json
184
    assert resp.json['err'] == 0
185
    assert len(resp.json['data']) == 2
186
    assert resp.json['data'][0]['title'] == 'test'
187
    assert resp.json['data'][0]['url'] == 'http://example.net/test/'
188
    assert resp.json['data'][0]['redirection'] is False
189
    assert resp.json['data'][0]['category'] == 'Category'
190
    assert resp.json['data'][0]['category_slug'] == 'category'
191
    assert 'count' not in resp.json['data'][0]
192

  
193
    resp = get_app(pub).get(sign_uri('/api/categories/category/formdefs/?include-count=on'))
194
    assert resp.json['data'][0]['title'] == 'test'
195
    assert resp.json['data'][0]['url'] == 'http://example.net/test/'
196
    assert resp.json['data'][0]['count'] == 0
197

  
198
    resp = get_app(pub).get(sign_uri('/api/categories/category/formdefs/?include-disabled=on'))
199
    assert len(resp.json['data']) == 3
200
    assert resp.json['data'][2]['title'] == 'test disabled'
201

  
202
    get_app(pub).get('/api/categories/XXX/formdefs/', status=404)
203

  
204
    resp = get_app(pub).get(sign_uri('/api/categories/category/formdefs/?backoffice-submission=on'))
205
    assert resp.json['err'] == 0
206
    assert len(resp.json['data']) == 0
207

  
208
    Role.wipe()
209
    role = Role(name='test')
210
    role.store()
211
    local_user.roles = []
212
    local_user.store()
213
    # check it's not advertised ...
214
    formdef.backoffice_submission_roles = [role.id]
215
    formdef.store()
216
    resp = get_app(pub).get(sign_uri('/api/categories/category/formdefs/?backoffice-submission=on'))
217
    assert resp.json['err'] == 0
218
    assert len(resp.json['data']) == 0
219
    resp = get_app(pub).get(
220
        sign_uri(
221
            '/api/categories/category/formdefs/?backoffice-submission=on&NameID=%s'
222
            % local_user.name_identifiers[0]
223
        )
224
    )
225
    assert resp.json['err'] == 0
226
    assert len(resp.json['data']) == 0
227
    # ... unless user has correct roles
228
    local_user.roles = [role.id]
229
    local_user.store()
230
    resp = get_app(pub).get(
231
        sign_uri(
232
            '/api/categories/category/formdefs/?backoffice-submission=on&NameID=%s'
233
            % local_user.name_identifiers[0]
234
        )
235
    )
236
    assert resp.json['err'] == 0
237
    assert len(resp.json['data']) == 1
238

  
239

  
240
def test_categories_full(pub):
241
    test_categories(pub)
242
    resp = get_app(pub).get('/api/categories/?full=on')
243
    assert len(resp.json['data'][0]['forms']) == 2
244
    assert resp.json['data'][0]['forms'][0]['title'] == 'test'
245
    assert resp.json['data'][0]['forms'][1]['title'] == 'test 2'
tests/api/test_custom_view.py
1
# -*- coding: utf-8 -*-
2

  
3
import os
4
import xml.etree.ElementTree as ET
5
import zipfile
6

  
7
import pytest
8
from django.utils.six import BytesIO
9
from quixote import get_publisher
10
from utilities import clean_temporary_pub, create_temporary_pub, get_app
11

  
12
from wcs import fields
13
from wcs.carddef import CardDef
14
from wcs.formdef import FormDef
15
from wcs.qommon import ods
16
from wcs.qommon.http_request import HTTPRequest
17
from wcs.roles import Role
18

  
19
from .utils import sign_uri
20

  
21

  
22
def pytest_generate_tests(metafunc):
23
    if 'pub' in metafunc.fixturenames:
24
        metafunc.parametrize('pub', ['pickle', 'sql'], indirect=True)
25

  
26

  
27
@pytest.fixture
28
def pub(request, emails):
29
    pub = create_temporary_pub(sql_mode=(request.param == 'sql'))
30

  
31
    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
32
    pub.set_app_dir(req)
33
    pub.cfg['identification'] = {'methods': ['password']}
34
    pub.cfg['language'] = {'language': 'en'}
35
    pub.write_cfg()
36

  
37
    open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w').write(
38
        '''\
39
[api-secrets]
40
coucou = 1234
41
'''
42
    )
43

  
44
    return pub
45

  
46

  
47
def teardown_module(module):
48
    clean_temporary_pub()
49

  
50

  
51
@pytest.fixture
52
def local_user():
53
    get_publisher().user_class.wipe()
54
    user = get_publisher().user_class()
55
    user.name = 'Jean Darmette'
56
    user.email = 'jean.darmette@triffouilis.fr'
57
    user.name_identifiers = ['0123456789']
58
    user.store()
59
    return user
60

  
61

  
62
@pytest.fixture
63
def test_api_custom_view_access(pub, local_user):
64
    Role.wipe()
65
    role = Role(name='test')
66
    role.store()
67
    local_user.roles = [role.id]
68
    local_user.store()
69

  
70
    FormDef.wipe()
71
    formdef = FormDef()
72
    formdef.name = 'test'
73
    formdef.workflow_roles = {'_receiver': role.id}
74
    formdef.fields = [fields.StringField(id='0', label='foobar', varname='foobar')]
75
    formdef.geolocations = {'base': 'Location'}
76
    formdef.store()
77

  
78
    carddef = CardDef()
79
    carddef.name = 'test'
80
    carddef.fields = [fields.StringField(id='0', label='foobar', varname='foo')]
81
    carddef.workflow_roles = {'_viewer': role.id}
82
    carddef.digest_template = 'bla {{ form_var_foo }} xxx'
83
    carddef.geolocations = {'base': 'Location'}
84
    carddef.store()
85

  
86
    pub.custom_view_class.wipe()
87
    custom_view = pub.custom_view_class()
88
    custom_view.title = 'shared formdef custom view'
89
    custom_view.formdef = formdef
90
    custom_view.columns = {'list': [{'id': '0'}]}
91
    custom_view.filters = {}
92
    custom_view.visibility = 'any'
93
    custom_view.store()
94

  
95
    custom_view = pub.custom_view_class()
96
    custom_view.title = 'private formdef custom view'
97
    custom_view.formdef = formdef
98
    custom_view.columns = {'list': [{'id': '0'}]}
99
    custom_view.filters = {}
100
    custom_view.visibility = 'owner'
101
    custom_view.user = local_user
102
    custom_view.store()
103

  
104
    custom_view = pub.custom_view_class()
105
    custom_view.title = 'shared carddef custom view'
106
    custom_view.formdef = carddef
107
    custom_view.columns = {'list': [{'id': '0'}]}
108
    custom_view.filters = {}
109
    custom_view.visibility = 'any'
110
    custom_view.store()
111

  
112
    custom_view = pub.custom_view_class()
113
    custom_view.title = 'private carddef custom view'
114
    custom_view.formdef = carddef
115
    custom_view.columns = {'list': [{'id': '0'}]}
116
    custom_view.filters = {}
117
    custom_view.visibility = 'owner'
118
    custom_view.user = local_user
119
    custom_view.store()
120

  
121
    custom_view = pub.custom_view_class()
122
    custom_view.title = 'datasource carddef custom view'
123
    custom_view.formdef = carddef
124
    custom_view.columns = {'list': [{'id': '0'}]}
125
    custom_view.filters = {}
126
    custom_view.visibility = 'datasource'
127
    custom_view.store()
128

  
129
    get_app(pub).get(sign_uri('/api/forms/test/list/shared-formdef-custom-view', user=local_user), status=200)
130
    get_app(pub).get(sign_uri('/api/forms/test/ods/shared-formdef-custom-view', user=local_user), status=200)
131
    get_app(pub).get(
132
        sign_uri('/api/forms/test/geojson/shared-formdef-custom-view', user=local_user), status=200
133
    )
134
    get_app(pub).get(
135
        sign_uri('/api/forms/test/list/private-formdef-custom-view', user=local_user), status=404
136
    )
137
    get_app(pub).get(sign_uri('/api/forms/test/ods/private-formdef-custom-view', user=local_user), status=404)
138
    get_app(pub).get(
139
        sign_uri('/api/forms/test/geojson/private-formdef-custom-view', user=local_user), status=404
140
    )
141

  
142
    get_app(pub).get(sign_uri('/api/cards/test/list/shared-carddef-custom-view', user=local_user), status=200)
143
    get_app(pub).get(sign_uri('/api/cards/test/ods/shared-carddef-custom-view', user=local_user), status=200)
144
    get_app(pub).get(
145
        sign_uri('/api/cards/test/geojson/shared-carddef-custom-view', user=local_user), status=200
146
    )
147
    get_app(pub).get(
148
        sign_uri('/api/cards/test/list/private-carddef-custom-view', user=local_user), status=404
149
    )
150
    get_app(pub).get(sign_uri('/api/cards/test/ods/private-carddef-custom-view', user=local_user), status=404)
151
    get_app(pub).get(
152
        sign_uri('/api/cards/test/geojson/private-carddef-custom-view', user=local_user), status=404
153
    )
154
    get_app(pub).get(
155
        sign_uri('/api/cards/test/list/datasource-carddef-custom-view', user=local_user), status=200
156
    )
157
    get_app(pub).get(
158
        sign_uri('/api/cards/test/ods/datasource-carddef-custom-view', user=local_user), status=200
159
    )
160
    get_app(pub).get(
161
        sign_uri('/api/cards/test/geojson/datasource-carddef-custom-view', user=local_user), status=200
162
    )
163

  
164

  
165
def test_api_list_formdata_custom_view(pub, local_user):
166
    Role.wipe()
167
    role = Role(name='test')
168
    role.store()
169

  
170
    FormDef.wipe()
171
    formdef = FormDef()
172
    formdef.name = 'test'
173
    formdef.workflow_roles = {'_receiver': role.id}
174
    formdef.fields = [
175
        fields.StringField(id='0', label='foobar', varname='foobar'),
176
    ]
177
    formdef.store()
178

  
179
    data_class = formdef.data_class()
180
    data_class.wipe()
181

  
182
    for i in range(30):
183
        formdata = data_class()
184
        formdata.data = {'0': 'FOO BAR %d' % i}
185
        formdata.user_id = local_user.id
186
        formdata.just_created()
187
        if i % 3 == 0:
188
            formdata.jump_status('new')
189
        else:
190
            formdata.jump_status('finished')
191
        formdata.store()
192

  
193
    # add proper role to user
194
    local_user.roles = [role.id]
195
    local_user.store()
196

  
197
    # check it now gets the data
198
    resp = get_app(pub).get(sign_uri('/api/forms/test/list', user=local_user))
199
    assert len(resp.json) == 30
200

  
201
    pub.custom_view_class.wipe()
202
    custom_view = pub.custom_view_class()
203
    custom_view.title = 'custom view'
204
    custom_view.formdef = formdef
205
    custom_view.columns = {'list': [{'id': '0'}]}
206
    custom_view.filters = {"filter": "done", "filter-status": "on"}
207
    custom_view.visibility = 'any'
208
    custom_view.store()
209

  
210
    resp = get_app(pub).get(sign_uri('/api/forms/test/list/custom-view', user=local_user))
211
    assert len(resp.json['data']) == 20
212

  
213

  
214
def test_api_ods_formdata_custom_view(pub, local_user):
215
    Role.wipe()
216
    role = Role(name='test')
217
    role.store()
218

  
219
    FormDef.wipe()
220
    formdef = FormDef()
221
    formdef.name = 'test'
222
    formdef.workflow_roles = {'_receiver': role.id}
223
    formdef.fields = [
224
        fields.StringField(id='0', label='foobar', varname='foobar'),
225
    ]
226
    formdef.store()
227

  
228
    data_class = formdef.data_class()
229
    data_class.wipe()
230

  
231
    for i in range(30):
232
        formdata = data_class()
233
        formdata.data = {'0': 'FOO BAR %d' % i}
234
        formdata.user_id = local_user.id
235
        formdata.just_created()
236
        if i % 3 == 0:
237
            formdata.jump_status('new')
238
        else:
239
            formdata.jump_status('finished')
240
        formdata.store()
241

  
242
    # add proper role to user
243
    local_user.roles = [role.id]
244
    local_user.store()
245

  
246
    # check it now gets the data
247
    resp = get_app(pub).get(sign_uri('/api/forms/test/ods', user=local_user))
248
    zipf = zipfile.ZipFile(BytesIO(resp.body))
249
    ods_sheet = ET.parse(zipf.open('content.xml'))
250
    assert len(ods_sheet.findall('.//{%s}table-row' % ods.NS['table'])) == 11
251

  
252
    pub.custom_view_class.wipe()
253
    custom_view = pub.custom_view_class()
254
    custom_view.title = 'custom view'
255
    custom_view.formdef = formdef
256
    custom_view.columns = {'list': [{'id': '0'}]}
257
    custom_view.filters = {"filter": "done", "filter-status": "on"}
258
    custom_view.visibility = 'any'
259
    custom_view.store()
260

  
261
    resp = get_app(pub).get(sign_uri('/api/forms/test/ods/custom-view', user=local_user))
262
    zipf = zipfile.ZipFile(BytesIO(resp.body))
263
    ods_sheet = ET.parse(zipf.open('content.xml'))
264
    assert len(ods_sheet.findall('.//{%s}table-row' % ods.NS['table'])) == 21
265

  
266

  
267
def test_api_geojson_formdata_custom_view(pub, local_user):
268
    Role.wipe()
269
    role = Role(name='test')
270
    role.store()
271

  
272
    FormDef.wipe()
273
    formdef = FormDef()
274
    formdef.name = 'test'
275
    formdef.workflow_roles = {'_receiver': role.id}
276
    formdef.fields = [
277
        fields.StringField(id='0', label='foobar', varname='foobar'),
278
    ]
279
    formdef.geolocations = {'base': 'Location'}
280
    formdef.store()
281

  
282
    data_class = formdef.data_class()
283
    data_class.wipe()
284

  
285
    for i in range(30):
286
        formdata = data_class()
287
        formdata.data = {'0': 'FOO BAR %d' % i}
288
        formdata.geolocations = {'base': {'lat': 48, 'lon': 2}}
289
        formdata.user_id = local_user.id
290
        formdata.just_created()
291
        if i % 3 == 0:
292
            formdata.jump_status('new')
293
        else:
294
            formdata.jump_status('finished')
295
        formdata.store()
296

  
297
    # add proper role to user
298
    local_user.roles = [role.id]
299
    local_user.store()
300

  
301
    # check it now gets the data
302
    resp = get_app(pub).get(sign_uri('/api/forms/test/geojson', user=local_user))
303
    assert len(resp.json['features']) == 10
304

  
305
    pub.custom_view_class.wipe()
306
    custom_view = pub.custom_view_class()
307
    custom_view.title = 'custom view'
308
    custom_view.formdef = formdef
309
    custom_view.columns = {'list': [{'id': '0'}]}
310
    custom_view.filters = {"filter": "done", "filter-status": "on"}
311
    custom_view.visibility = 'any'
312
    custom_view.store()
313

  
314
    resp = get_app(pub).get(sign_uri('/api/forms/test/geojson/custom-view', user=local_user))
315
    assert len(resp.json['features']) == 20
tests/api/test_formdata.py
1
# -*- coding: utf-8 -*-
2

  
3
import base64
4
import datetime
5
import os
6
import re
7
import time
8
import xml.etree.ElementTree as ET
9
import zipfile
10

  
11
import pytest
12
from django.utils.encoding import force_bytes
13
from django.utils.six import BytesIO
14
from quixote import get_publisher
15
from utilities import clean_temporary_pub, create_temporary_pub, get_app, login
16

  
17
from wcs import fields
18
from wcs.blocks import BlockDef
19
from wcs.data_sources import NamedDataSource
20
from wcs.formdata import Evolution
21
from wcs.formdef import FormDef
22
from wcs.qommon import ods
23
from wcs.qommon.form import PicklableUpload
24
from wcs.qommon.http_request import HTTPRequest
25
from wcs.qommon.ident.password_accounts import PasswordAccount
26
from wcs.roles import Role
27
from wcs.workflows import EditableWorkflowStatusItem, Workflow, WorkflowBackofficeFieldsFormDef
28

  
29
from .utils import sign_uri
30

  
31

  
32
def pytest_generate_tests(metafunc):
33
    if 'pub' in metafunc.fixturenames:
34
        metafunc.parametrize('pub', ['pickle', 'sql'], indirect=True)
35

  
36

  
37
@pytest.fixture
38
def pub(request, emails):
39
    pub = create_temporary_pub(sql_mode=(request.param == 'sql'))
40

  
41
    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
42
    pub.set_app_dir(req)
43
    pub.cfg['identification'] = {'methods': ['password']}
44
    pub.cfg['language'] = {'language': 'en'}
45
    pub.write_cfg()
46

  
47
    open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w').write(
48
        '''\
49
[api-secrets]
50
coucou = 1234
51
'''
52
    )
53

  
54
    return pub
55

  
56

  
57
def teardown_module(module):
58
    clean_temporary_pub()
59

  
60

  
61
@pytest.fixture
62
def local_user():
63
    get_publisher().user_class.wipe()
64
    user = get_publisher().user_class()
65
    user.name = 'Jean Darmette'
66
    user.email = 'jean.darmette@triffouilis.fr'
67
    user.name_identifiers = ['0123456789']
68
    user.store()
69
    return user
70

  
71

  
72
@pytest.fixture
73
def admin_user():
74
    get_publisher().user_class.wipe()
75
    user = get_publisher().user_class()
76
    user.name = 'John Doe Admin'
77
    user.email = 'john.doe@example.com'
78
    user.name_identifiers = ['0123456789']
79
    user.is_admin = True
80
    user.store()
81

  
82
    account = PasswordAccount(id='admin')
83
    account.set_password('admin')
84
    account.user_id = user.id
85
    account.store()
86

  
87
    return user
88

  
89

  
90
@pytest.fixture
91
def ics_data(local_user):
92
    Role.wipe()
93
    role = Role(name='test')
94
    role.store()
95

  
96
    FormDef.wipe()
97
    formdef = FormDef()
98
    formdef.url_name = 'test'
99
    formdef.name = 'testé'
100
    formdef.workflow_roles = {'_receiver': role.id}
101
    formdef.fields = [
102
        fields.StringField(id='0', label='foobar', varname='foobar'),
103
        fields.StringField(id='1', label='foobar2', varname='foobar2'),
104
    ]
105
    formdef.digest_template = 'plöp {{ form_var_foobar }} plÔp'
106
    formdef.store()
107

  
108
    data_class = formdef.data_class()
109
    data_class.wipe()
110

  
111
    date = datetime.datetime(2014, 1, 20, 12, 00)
112
    for i in range(30):
113
        formdata = data_class()
114
        formdata.data = {'0': (date + datetime.timedelta(days=i)).strftime('%Y-%m-%d %H:%M')}
115
        formdata.data['1'] = (date + datetime.timedelta(days=i, minutes=i + 1)).strftime('%Y-%m-%d %H:%M')
116
        formdata.user_id = local_user.id
117
        formdata.just_created()
118
        if i % 3 == 0:
119
            formdata.jump_status('new')
120
        else:
121
            formdata.jump_status('finished')
122
        formdata.store()
123

  
124
    # not a datetime: ignored
125
    date = datetime.date(2014, 1, 20)
126
    formdata = data_class()
127
    formdata.data = {'0': '12:00'}
128
    formdata.data['1'] = '13:00'
129
    formdata.user_id = local_user.id
130
    formdata.just_created()
131
    formdata.jump_status('new')
132
    formdata.store()
133

  
134

  
135
def test_formdata(pub, local_user):
136
    NamedDataSource.wipe()
137
    data_source = NamedDataSource(name='foobar')
138
    data_source.data_source = {
139
        'type': 'formula',
140
        'value': repr([{'id': '1', 'text': 'foo', 'more': 'XXX'}, {'id': '2', 'text': 'bar', 'more': 'YYY'}]),
141
    }
142
    data_source.store()
143

  
144
    BlockDef.wipe()
145
    block = BlockDef()
146
    block.name = 'foobar'
147
    block.fields = [
148
        fields.StringField(id='abc', label='Foo', varname='foo'),
149
        fields.ItemField(id='xyz', label='Test', type='item', data_source={'type': 'foobar'}, varname='bar'),
150
    ]
151
    block.store()
152

  
153
    Role.wipe()
154
    role = Role(name='test')
155
    role.id = '123'
156
    role.store()
157
    another_role = Role(name='another')
158
    another_role.id = '321'
159
    another_role.store()
160
    FormDef.wipe()
161
    formdef = FormDef()
162
    formdef.geolocations = {'base': 'blah'}
163
    formdef.name = 'test'
164
    formdef.fields = [
165
        fields.StringField(id='0', label='foobar', varname='foobar'),
166
        fields.StringField(id='1', label='foobar2'),
167
        fields.DateField(id='2', label='foobar3', varname='date'),
168
        fields.FileField(id='3', label='foobar4', varname='file'),
169
        fields.ItemField(id='4', label='foobar5', varname='item', data_source={'type': 'foobar'}),
170
        fields.BlockField(id='5', label='test', varname='blockdata', type='block:foobar', max_items=3),
171
    ]
172
    Workflow.wipe()
173
    workflow = Workflow(name='foo')
174
    workflow.possible_status = Workflow.get_default_workflow().possible_status[:]
175
    workflow.roles['_foobar'] = 'Foobar'
176
    workflow.store()
177
    formdef.workflow_id = workflow.id
178
    formdef.workflow_roles = {'_receiver': role.id, '_foobar': another_role.id}
179
    formdef.store()
180
    item_field = formdef.fields[4]
181

  
182
    formdef.data_class().wipe()
183
    formdata = formdef.data_class()()
184
    date = time.strptime('2014-01-20', '%Y-%m-%d')
185
    upload = PicklableUpload('test.txt', 'text/plain', 'ascii')
186
    upload.receive([b'base64me'])
187
    formdata.data = {
188
        '0': 'foo@localhost',
189
        '1': 'xxx',
190
        '2': date,
191
        '3': upload,
192
        '4': '1',
193
        '5': {
194
            'data': [
195
                {'abc': 'plop', 'xyz': '1', 'xyz_display': 'foo', 'xyz_structured': 'XXX'},
196
            ],
197
            'schema': {},  # not important here
198
        },
199
        '5_display': 'hello',
200
    }
201
    formdata.data['4_display'] = item_field.store_display_value(formdata.data, item_field.id)
202
    formdata.data['4_structured'] = item_field.store_structured_value(formdata.data, item_field.id)
203
    formdata.user_id = local_user.id
204
    formdata.just_created()
205
    formdata.status = 'wf-new'
206
    formdata.evolution[-1].status = 'wf-new'
207
    formdata.geolocations = {'base': {'lon': 10, 'lat': -12}}
208
    formdata.store()
209

  
210
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user), status=403)
211

  
212
    local_user.roles = [role.id]
213
    local_user.store()
214
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user), status=200)
215

  
216
    assert datetime.datetime.strptime(resp.json['last_update_time'], '%Y-%m-%dT%H:%M:%S')
217
    assert datetime.datetime.strptime(resp.json['receipt_time'], '%Y-%m-%dT%H:%M:%S')
218
    assert len(resp.json['fields']) == 8
219
    assert 'foobar' in resp.json['fields']
220
    assert 'foobar2' not in resp.json['fields']  # foobar2 has no varname, not in json
221
    assert resp.json['user']['name'] == local_user.name
222
    assert resp.json['fields']['foobar'] == 'foo@localhost'
223
    assert resp.json['fields']['date'] == '2014-01-20'
224
    assert resp.json['fields']['file']['content'] == 'YmFzZTY0bWU='  # base64('base64me')
225
    assert resp.json['fields']['file']['filename'] == 'test.txt'
226
    assert resp.json['fields']['file']['content_type'] == 'text/plain'
227
    assert resp.json['fields']['item'] == 'foo'
228
    assert resp.json['fields']['item_raw'] == '1'
229
    assert resp.json['fields']['item_structured'] == {'id': '1', 'text': 'foo', 'more': 'XXX'}
230
    assert resp.json['fields']['blockdata'] == 'hello'
231
    assert resp.json['fields']['blockdata_raw'] == [
232
        {'foo': 'plop', 'bar': 'foo', 'bar_raw': '1', 'bar_structured': 'XXX'}
233
    ]
234
    assert resp.json['workflow']['status']['name'] == 'New'
235
    assert resp.json['workflow']['real_status']['name'] == 'New'
236
    assert resp.json['submission']['channel'] == 'web'
237
    assert resp.json['geolocations']['base']['lon'] == 10
238
    assert resp.json['geolocations']['base']['lat'] == -12
239

  
240
    assert [x.get('id') for x in resp.json['roles']['_receiver']] == [str(role.id)]
241
    assert [x.get('id') for x in resp.json['roles']['_foobar']] == [str(another_role.id)]
242
    assert set([x.get('id') for x in resp.json['roles']['concerned']]) == set(
243
        [str(role.id), str(another_role.id)]
244
    )
245
    assert [x.get('id') for x in resp.json['roles']['actions']] == [str(role.id)]
246

  
247
    # check the ?format=json endpoint returns 403
248
    get_app(pub).get('/test/%s/?format=json' % formdata.id, status=403)
249
    get_app(pub).get(sign_uri('/test/%s/' % formdata.id, user=local_user), status=403)
250

  
251
    # check status visibility
252
    workflow.add_status('Status1', 'st1')
253
    workflow.possible_status[-1].visibility = ['unknown']
254
    workflow.store()
255
    formdata.jump_status('st1')
256
    assert formdata.status == 'wf-st1'
257

  
258
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user), status=200)
259
    assert resp.json['workflow']['status'] == {'id': 'new', 'name': 'New'}
260
    assert resp.json['workflow']['real_status'] == {'id': 'st1', 'name': 'Status1'}
261

  
262

  
263
def test_formdata_backoffice_fields(pub, local_user):
264
    test_formdata(pub, local_user)
265
    Workflow.wipe()
266
    workflow = Workflow(name='foo')
267
    workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
268
    workflow.backoffice_fields_formdef.fields = [
269
        fields.StringField(id='bo1', label='1st backoffice field', type='string', varname='backoffice_blah'),
270
    ]
271
    workflow.store()
272

  
273
    formdef = FormDef.select()[0]
274
    formdata = formdef.data_class().select()[0]
275
    formdata.data['bo1'] = 'Hello world'
276
    formdata.store()
277

  
278
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user))
279
    assert resp.json['workflow']['fields']['backoffice_blah'] == 'Hello world'
280

  
281

  
282
def test_formdata_duplicated_varnames(pub, local_user):
283
    Role.wipe()
284
    role = Role(name='test')
285
    role.id = '123'
286
    role.store()
287
    another_role = Role(name='another')
288
    another_role.id = '321'
289
    another_role.store()
290
    FormDef.wipe()
291
    formdef = FormDef()
292
    formdef.geolocations = {'base': 'blah'}
293
    formdef.name = 'test'
294
    formdef.fields = [
295
        fields.StringField(id='0', label='foobar', varname='foobar'),
296
        fields.StringField(id='1', label='foobar2', varname='foobar'),
297
    ]
298
    workflow = Workflow.get_default_workflow()
299
    workflow.roles['_foobar'] = 'Foobar'
300
    workflow.id = '2'
301
    workflow.store()
302
    formdef.workflow_id = workflow.id
303
    formdef.workflow_roles = {'_receiver': role.id, '_foobar': another_role.id}
304
    formdef.store()
305

  
306
    formdef.data_class().wipe()
307
    formdata = formdef.data_class()()
308
    formdata.data = {
309
        '0': 'foo',
310
        '1': 'bar',
311
    }
312
    formdata.user_id = local_user.id
313
    formdata.just_created()
314
    formdata.status = 'wf-new'
315
    formdata.evolution[-1].status = 'wf-new'
316
    formdata.store()
317

  
318
    local_user.roles = [role.id]
319
    local_user.store()
320
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user), status=200)
321
    assert resp.json['fields'] == {'foobar': 'foo'}
322

  
323
    formdata.data = {
324
        '0': 'foo',
325
        '1': '',
326
    }
327
    formdata.store()
328
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user), status=200)
329
    assert resp.json['fields'] == {'foobar': 'foo'}
330

  
331
    formdata.data = {
332
        '0': '',
333
        '1': 'foo',
334
    }
335
    formdata.store()
336
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user), status=200)
337
    assert resp.json['fields'] == {'foobar': 'foo'}
338

  
339

  
340
def test_formdata_edit(pub, local_user):
341
    Role.wipe()
342
    role = Role(name='test')
343
    role.id = '123'
344
    role.store()
345
    another_role = Role(name='another')
346
    another_role.id = '321'
347
    another_role.store()
348
    local_user.roles = [role.id]
349
    local_user.store()
350
    FormDef.wipe()
351
    formdef = FormDef()
352
    formdef.name = 'test'
353
    formdef.fields = [
354
        fields.StringField(id='0', label='foobar', varname='foobar'),
355
    ]
356
    Workflow.wipe()
357
    workflow = Workflow(name='foo')
358
    workflow.possible_status = Workflow.get_default_workflow().possible_status[:]
359
    workflow.roles['_foobar'] = 'Foobar'
360
    workflow.store()
361
    formdef.workflow_id = workflow.id
362
    formdef.workflow_roles = {'_receiver': role.id, '_foobar': another_role.id}
363
    formdef.store()
364
    formdef.data_class().wipe()
365
    formdata = formdef.data_class()()
366
    formdata.data = {
367
        '0': 'foo@localhost',
368
    }
369
    formdata.user_id = local_user.id
370
    formdata.just_created()
371
    formdata.status = 'wf-new'
372
    formdata.evolution[-1].status = 'wf-new'
373
    formdata.store()
374

  
375
    # not user
376
    get_app(pub).post_json(
377
        sign_uri('/api/forms/test/%s/' % formdata.id), {'data': {'0': 'bar@localhost'}}, status=403
378
    )
379

  
380
    # no editable action
381
    get_app(pub).post_json(
382
        sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user),
383
        {'data': {'0': 'bar@localhost'}},
384
        status=403,
385
    )
386

  
387
    wfedit = EditableWorkflowStatusItem()
388
    wfedit.id = '_wfedit'
389
    wfedit.by = [local_user.roles[0]]
390
    workflow.possible_status[1].items.append(wfedit)
391
    wfedit.parent = workflow.possible_status[1]
392
    workflow.store()
393

  
394
    get_app(pub).post_json(
395
        sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user),
396
        {'data': {'0': 'bar@localhost'}},
397
        status=200,
398
    )
399
    assert formdef.data_class().select()[0].data['0'] == 'bar@localhost'
400

  
401
    # not editable by user role
402
    wfedit.by = ['XX']
403
    workflow.store()
404
    get_app(pub).post_json(
405
        sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user),
406
        {'data': {'0': 'bar@localhost'}},
407
        status=403,
408
    )
409

  
410
    # edit + jump
411
    wfedit.status = 'rejected'
412
    wfedit.by = [local_user.roles[0]]
413
    workflow.store()
414

  
415
    get_app(pub).post_json(
416
        sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user),
417
        {'data': {'0': 'bar2@localhost'}},
418
        status=200,
419
    )
420
    assert formdef.data_class().select()[0].data['0'] == 'bar2@localhost'
421
    assert formdef.data_class().select()[0].status == 'wf-rejected'
422

  
423

  
424
def test_formdata_with_workflow_data(pub, local_user):
425
    Role.wipe()
426
    role = Role(name='test')
427
    role.id = '123'
428
    role.store()
429

  
430
    local_user.roles = [role.id]
431
    local_user.store()
432

  
433
    FormDef.wipe()
434
    formdef = FormDef()
435
    formdef.name = 'test'
436
    formdef.fields = []
437
    workflow = Workflow.get_default_workflow()
438
    workflow.id = '2'
439
    workflow.store()
440
    formdef.workflow_id = workflow.id
441
    formdef.workflow_roles = {'_receiver': role.id}
442
    formdef.store()
443

  
444
    formdef.data_class().wipe()
445
    formdata = formdef.data_class()()
446
    formdata.just_created()
447
    formdata.status = 'wf-new'
448
    formdata.evolution[-1].status = 'wf-new'
449

  
450
    from wcs.qommon.form import PicklableUpload as PicklableUpload3
451

  
452
    upload = PicklableUpload3('test.txt', 'text/plain', 'ascii')
453
    upload.receive([b'test'])
454
    upload2 = PicklableUpload3('test.txt', 'text/plain', 'ascii')
455
    upload2.receive([b'test'])
456
    formdata.workflow_data = {'blah': upload, 'blah2': upload2, 'xxx': 23}
457
    formdata.store()
458

  
459
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user))
460
    assert resp.json['workflow']['data']['xxx'] == 23
461
    assert resp.json['workflow']['data']['blah']['filename'] == 'test.txt'
462
    assert resp.json['workflow']['data']['blah']['content_type'] == 'text/plain'
463
    assert base64.decodebytes(force_bytes(resp.json['workflow']['data']['blah']['content'])) == b'test'
464
    assert base64.decodebytes(force_bytes(resp.json['workflow']['data']['blah2']['content'])) == b'test'
465

  
466

  
467
def test_api_list_formdata(pub, local_user):
468
    Role.wipe()
469
    role = Role(name='test')
470
    role.store()
471

  
472
    FormDef.wipe()
473
    formdef = FormDef()
474
    formdef.name = 'test'
475
    formdef.workflow_roles = {'_receiver': role.id}
476
    formdef.fields = [
477
        fields.StringField(id='0', label='foobar', varname='foobar', type='string'),
478
        fields.ItemField(
479
            id='1', label='foobar3', varname='foobar3', type='item', items=['foo', 'bar', 'baz']
480
        ),
481
        fields.FileField(id='2', label='foobar4', varname='file', type='file'),
482
    ]
483
    formdef.store()
484

  
485
    data_class = formdef.data_class()
486
    data_class.wipe()
487

  
488
    for i in range(30):
489
        formdata = data_class()
490
        upload = PicklableUpload('test.txt', 'text/plain', 'ascii')
491
        upload.receive([b'base64me'])
492
        formdata.data = {'0': 'FOO BAR %d' % i, '2': upload}
493
        formdata.user_id = local_user.id
494
        if i % 4 == 0:
495
            formdata.data['1'] = 'foo'
496
            formdata.data['1_display'] = 'foo'
497
        elif i % 4 == 1:
498
            formdata.data['1'] = 'bar'
499
            formdata.data['1_display'] = 'bar'
500
        else:
501
            formdata.data['1'] = 'baz'
502
            formdata.data['1_display'] = 'baz'
503

  
504
        formdata.just_created()
505
        if i % 3 == 0:
506
            formdata.jump_status('new')
507
        elif i % 3 == 1:
508
            formdata.jump_status('just_submitted')
509
        else:
510
            formdata.jump_status('finished')
511
        if i % 7 == 0:
512
            formdata.backoffice_submission = True
513
            formdata.submission_channel = 'mail'
514
        formdata.evolution[-1].time = (
515
            datetime.datetime(2020, 1, 2, 3, 4) + datetime.timedelta(hours=i)
516
        ).timetuple()
517
        formdata.store()
518

  
519
    # check access is denied if the user has not the appropriate role
520
    resp = get_app(pub).get(sign_uri('/api/forms/test/list', user=local_user), status=403)
521

  
522
    # add proper role to user
523
    local_user.roles = [role.id]
524
    local_user.store()
525

  
526
    # check it now gets the data
527
    resp = get_app(pub).get(sign_uri('/api/forms/test/list', user=local_user))
528
    assert len(resp.json) == 30
529
    assert datetime.datetime.strptime(resp.json[0]['receipt_time'], '%Y-%m-%dT%H:%M:%S')
530
    assert 'fields' not in resp.json[0]
531

  
532
    # check getting full formdata
533
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?full=on', user=local_user))
534
    assert len(resp.json) == 30
535
    assert 'receipt_time' in resp.json[0]
536
    assert 'fields' in resp.json[0]
537
    assert 'url' in resp.json[0]['fields']['file']
538
    assert 'content' not in resp.json[0]['fields']['file']  # no file content in full lists
539
    assert 'user' in resp.json[0]
540
    assert 'evolution' in resp.json[0]
541
    assert len(resp.json[0]['evolution']) == 2
542
    assert 'status' in resp.json[0]['evolution'][0]
543
    assert 'who' in resp.json[0]['evolution'][0]
544
    assert 'time' in resp.json[0]['evolution'][0]
545
    assert resp.json[0]['evolution'][0]['who']['id'] == local_user.id
546

  
547
    assert all('status' in x['workflow'] for x in resp.json)
548
    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 0'][0]['submission'][
549
        'backoffice'
550
    ] is True
551
    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 0'][0]['submission'][
552
        'channel'
553
    ] == 'mail'
554
    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 1'][0]['submission'][
555
        'backoffice'
556
    ] is False
557
    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 1'][0]['submission']['channel'] == 'web'
558

  
559
    # check filtered results
560
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter-foobar3=foo', user=local_user))
561
    assert len(resp.json) == 8
562
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter-foobar3=bar', user=local_user))
563
    assert len(resp.json) == 8
564
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter-foobar3=baz', user=local_user))
565
    assert len(resp.json) == 14
566

  
567
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter-foobar=FOO BAR 3', user=local_user))
568
    assert len(resp.json) == 1
569

  
570
    # check filter on status
571
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter=pending', user=local_user))
572
    assert len(resp.json) == 20
573
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter=done', user=local_user))
574
    assert len(resp.json) == 10
575
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?filter=all', user=local_user))
576
    assert len(resp.json) == 30
577

  
578
    # check filter on last update time
579
    resp = get_app(pub).get(
580
        sign_uri(
581
            '/api/forms/test/list?filter-start-mtime=on&filter-start-mtime-value=2020-01-03', user=local_user
582
        )
583
    )
584
    assert len(resp.json) == 16
585
    resp = get_app(pub).get(
586
        sign_uri(
587
            '/api/forms/test/list?filter-start-mtime=on&filter-start-mtime-value=2020-01-03 10:00',
588
            user=local_user,
589
        )
590
    )
591
    assert len(resp.json) == 10
592
    resp = get_app(pub).get(
593
        sign_uri(
594
            '/api/forms/test/list?filter-end-mtime=on&filter-end-mtime-value=2020-01-03', user=local_user
595
        )
596
    )
597
    assert len(resp.json) == 14
598
    resp = get_app(pub).get(
599
        sign_uri(
600
            '/api/forms/test/list?filter-end-mtime=on&filter-end-mtime-value=2020-01-03 10:00',
601
            user=local_user,
602
        )
603
    )
604
    assert len(resp.json) == 20
605

  
606
    # check limit and offset
607
    resp_all = get_app(pub).get(sign_uri('/api/forms/test/list?filter=all', user=local_user))
608
    assert len(resp_all.json) == 30
609
    partial_resps = []
610
    for i in range(0, 48, 12):
611
        partial_resps.append(
612
            get_app(pub).get(
613
                sign_uri('/api/forms/test/list?filter=all&offset=%s&limit=12' % i, user=local_user)
614
            )
615
        )
616
    assert len(partial_resps[0].json) == 12
617
    assert len(partial_resps[1].json) == 12
618
    assert len(partial_resps[2].json) == 6
619
    assert len(partial_resps[3].json) == 0
620
    resp_all_ids = [x.get('id') for x in resp_all.json]
621
    resp_partial_ids = []
622
    for resp in partial_resps:
623
        resp_partial_ids.extend([x.get('id') for x in resp.json])
624
    assert resp_all_ids == resp_partial_ids
625

  
626
    # check error handling
627
    get_app(pub).get(sign_uri('/api/forms/test/list?filter=all&offset=plop', user=local_user), status=400)
628
    get_app(pub).get(sign_uri('/api/forms/test/list?filter=all&limit=plop', user=local_user), status=400)
629

  
630

  
631
def test_api_anonymized_formdata(pub, local_user, admin_user):
632
    Role.wipe()
633
    role = Role(name='test')
634
    role.store()
635

  
636
    FormDef.wipe()
637
    formdef = FormDef()
638
    formdef.name = 'test'
639
    formdef.workflow_roles = {'_receiver': role.id}
640
    formdef.fields = [
641
        fields.StringField(id='0', label='foobar', varname='foobar'),
642
        fields.ItemField(
643
            id='1', label='foobar3', varname='foobar3', type='item', items=['foo', 'bar', 'baz']
644
        ),
645
        fields.FileField(id='2', label='foobar4', varname='file'),
646
    ]
647
    formdef.store()
648

  
649
    data_class = formdef.data_class()
650
    data_class.wipe()
651

  
652
    for i in range(30):
653
        formdata = data_class()
654
        upload = PicklableUpload('test.txt', 'text/plain', 'ascii')
655
        upload.receive([b'base64me'])
656
        formdata.data = {'0': 'FOO BAR %d' % i, '2': upload}
657
        formdata.user_id = local_user.id
658
        if i % 4 == 0:
659
            formdata.data['1'] = 'foo'
660
            formdata.data['1_display'] = 'foo'
661
        elif i % 4 == 1:
662
            formdata.data['1'] = 'bar'
663
            formdata.data['1_display'] = 'bar'
664
        else:
665
            formdata.data['1'] = 'baz'
666
            formdata.data['1_display'] = 'baz'
667

  
668
        formdata.just_created()
669
        if i % 3 == 0:
670
            formdata.jump_status('new')
671
        else:
672
            evo = Evolution()
673
            evo.who = admin_user.id
674
            evo.time = time.localtime()
675
            evo.status = 'wf-%s' % 'finished'
676
            formdata.evolution.append(evo)
677
            formdata.status = evo.status
678
        formdata.store()
679

  
680
    # check access is granted even if the user has not the appropriate role
681
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?anonymise&full=on', user=local_user))
682
    assert len(resp.json) == 30
683
    assert 'receipt_time' in resp.json[0]
684
    assert 'fields' in resp.json[0]
685
    assert 'user' not in resp.json[0]
686
    assert 'file' not in resp.json[0]['fields']  # no file export in full lists
687
    assert 'foobar3' in resp.json[0]['fields']
688
    assert 'foobar' not in resp.json[0]['fields']
689
    assert 'evolution' in resp.json[0]
690
    assert len(resp.json[0]['evolution']) == 2
691
    assert 'status' in resp.json[0]['evolution'][0]
692
    assert 'who' not in resp.json[0]['evolution'][0]
693
    assert 'time' in resp.json[0]['evolution'][0]
694
    # check evolution made by other than _submitter are exported
695
    assert 'who' in resp.json[1]['evolution'][1]
696
    assert 'id' in resp.json[1]['evolution'][1]['who']
697
    assert 'email' in resp.json[1]['evolution'][1]['who']
698
    assert 'NameID' in resp.json[1]['evolution'][1]['who']
699
    assert 'name' in resp.json[1]['evolution'][1]['who']
700

  
701
    # check access is granted event if there is no user
702
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?anonymise&full=on'))
703
    assert len(resp.json) == 30
704
    assert 'receipt_time' in resp.json[0]
705
    assert 'fields' in resp.json[0]
706
    assert 'user' not in resp.json[0]
707
    assert 'file' not in resp.json[0]['fields']  # no file export in full lists
708
    assert 'foobar3' in resp.json[0]['fields']
709
    assert 'foobar' not in resp.json[0]['fields']
710
    assert 'evolution' in resp.json[0]
711
    assert len(resp.json[0]['evolution']) == 2
712
    assert 'status' in resp.json[0]['evolution'][0]
713
    assert 'who' not in resp.json[0]['evolution'][0]
714
    assert 'time' in resp.json[0]['evolution'][0]
715
    # check anonymise is enforced on detail view
716
    resp = get_app(pub).get(sign_uri('/api/forms/test/%s/?anonymise&full=on' % resp.json[1]['id']))
717
    assert 'receipt_time' in resp.json
718
    assert 'fields' in resp.json
719
    assert 'user' not in resp.json
720
    assert 'file' not in resp.json['fields']  # no file export in detail
721
    assert 'foobar3' in resp.json['fields']
722
    assert 'foobar' not in resp.json['fields']
723
    assert 'evolution' in resp.json
724
    assert len(resp.json['evolution']) == 2
725
    assert 'status' in resp.json['evolution'][0]
726
    assert 'who' not in resp.json['evolution'][0]
727
    assert 'time' in resp.json['evolution'][0]
728
    # check evolution made by other than _submitter are exported
729
    assert 'who' in resp.json['evolution'][1]
730
    assert 'id' in resp.json['evolution'][1]['who']
731
    assert 'email' in resp.json['evolution'][1]['who']
732
    assert 'NameID' in resp.json['evolution'][1]['who']
733
    assert 'name' in resp.json['evolution'][1]['who']
734

  
735

  
736
def test_api_geojson_formdata(pub, local_user):
737
    Role.wipe()
738
    role = Role(name='test')
739
    role.store()
740

  
741
    FormDef.wipe()
742
    formdef = FormDef()
743
    formdef.name = 'test'
744
    formdef.workflow_roles = {'_receiver': role.id}
745
    formdef.fields = [
746
        fields.StringField(id='0', label='foobar', varname='foobar', type='string'),
747
        fields.FileField(id='1', label='foobar1', type='file'),
748
    ]
749
    formdef.store()
750

  
751
    data_class = formdef.data_class()
752
    data_class.wipe()
753

  
754
    formdef.geolocations = {'base': 'Location'}
755
    formdef.store()
756

  
757
    # check access is denied if the user has not the appropriate role
758
    resp = get_app(pub).get(sign_uri('/api/forms/test/geojson', user=local_user), status=403)
759
    # even if there's an anonymse parameter
760
    resp = get_app(pub).get(sign_uri('/api/forms/test/geojson?anonymise', user=local_user), status=403)
761

  
762
    upload = PicklableUpload('test.txt', 'text/plain', 'ascii')
763
    upload.receive([b'base64me'])
764

  
765
    foobar = '<font color="red">FOO BAR</font>'
766
    username = '<font color="red">Jean Darmette</font>'
767

  
768
    data = {'0': foobar, '1': upload}
769
    local_user.name = username
770
    local_user.store()
771
    for i in range(30):
772
        formdata = data_class()
773
        formdata.geolocations = {'base': {'lat': 48, 'lon': 2}}
774
        formdata.data = data
775
        formdata.user_id = local_user.id
776
        formdata.just_created()
777
        if i % 3 == 0:
778
            formdata.jump_status('new')
779
        else:
780
            formdata.jump_status('finished')
781
        formdata.store()
782

  
783
    # add proper role to user
784
    local_user.roles = [role.id]
785
    local_user.store()
786

  
787
    # check it gets the data
788
    resp = get_app(pub).get(sign_uri('/api/forms/test/geojson', user=local_user))
789
    assert 'features' in resp.json
790
    assert len(resp.json['features']) == 10
791
    display_fields = resp.json['features'][0]['properties']['display_fields']
792
    assert len(display_fields) == 5
793
    for field in display_fields:
794
        if field['label'] == 'Number':
795
            assert field['varname'] == 'id'
796
            assert field['html_value'] == '1-28'
797
            assert field['value'] == '1-28'
798
        if field['label'] == 'User Label':
799
            assert field['varname'] == 'user_label'
800
            assert field['value'] == username
801
            assert field['html_value'] == "&lt;font color=&quot;red&quot;&gt;Jean Darmette&lt;/font&gt;"
802
        if field['label'] == 'foobar':
803
            assert field['varname'] == 'foobar'
804
            assert field['value'] == foobar
805
            assert field['html_value'] == "&lt;font color=&quot;red&quot;&gt;FOO BAR&lt;/font&gt;"
806
        if field['label'] == 'foobar1':
807
            assert field['varname'] is None
808
            assert field['value'] == "test.txt"
809
            assert field['html_value'] == (
810
                '<div class="file-field"><a download="test.txt" href="http://example.net/backoffice/management/test/28/download?f=1">'
811
                '<span>test.txt</span></a></div>'
812
            )
813
    field_varnames = [f['varname'] for f in display_fields]
814
    assert 'foobar' not in field_varnames
815

  
816
    # check full=on
817
    resp = get_app(pub).get(sign_uri('/api/forms/test/geojson?full=on', user=local_user))
818
    assert len(resp.json['features']) == 10
819
    display_fields = resp.json['features'][0]['properties']['display_fields']
820
    assert len(display_fields) == 8
821
    field_varnames = [f['varname'] for f in display_fields]
822
    assert 'foobar' in field_varnames
823

  
824
    # check with a filter
825
    resp = get_app(pub).get(sign_uri('/api/forms/test/geojson?filter=done', user=local_user))
826
    assert 'features' in resp.json
827
    assert len(resp.json['features']) == 20
828

  
829
    # check with http basic auth
830
    app = get_app(pub)
831
    app.authorization = ('Basic', ('user', 'password'))
832
    resp = app.get('/api/forms/test/geojson?email=%s' % local_user.email, status=401)
833

  
834
    # add authentication info
835
    pub.load_site_options()
836
    pub.site_options.add_section('api-http-auth-geojson')
837
    pub.site_options.set('api-http-auth-geojson', 'user', 'password')
838
    pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
839

  
840
    resp = app.get('/api/forms/test/geojson?email=%s' % local_user.email)
841
    assert 'features' in resp.json
842
    assert len(resp.json['features']) == 10
843

  
844
    # check 404 if the formdef doesn't have geolocation support
845
    formdef.geolocations = {}
846
    formdef.store()
847
    resp = get_app(pub).get(sign_uri('/api/forms/test/geojson', user=local_user), status=404)
848

  
849

  
850
def test_api_ods_formdata(pub, local_user):
851
    Role.wipe()
852
    role = Role(name='test')
853
    role.store()
854

  
855
    FormDef.wipe()
856
    formdef = FormDef()
857
    formdef.name = 'test'
858
    formdef.workflow_roles = {'_receiver': role.id}
859
    formdef.fields = [
860
        fields.StringField(id='0', label='foobar', varname='foobar', type='string'),
861
    ]
862
    formdef.store()
863

  
864
    data_class = formdef.data_class()
865
    data_class.wipe()
866

  
867
    # check access is denied if the user has not the appropriate role
868
    resp = get_app(pub).get(sign_uri('/api/forms/test/ods', user=local_user), status=403)
869
    # even if there's an anonymise parameter
870
    resp = get_app(pub).get(sign_uri('/api/forms/test/ods?anonymise', user=local_user), status=403)
871

  
872
    data = {'0': 'foobar'}
873
    for i in range(30):
874
        formdata = data_class()
875
        formdata.data = data
876
        formdata.user_id = local_user.id
877
        formdata.just_created()
878
        if i % 3 == 0:
879
            formdata.jump_status('new')
880
        else:
881
            formdata.jump_status('finished')
882
        formdata.store()
883

  
884
    # add proper role to user
885
    local_user.roles = [role.id]
886
    local_user.store()
887

  
888
    # check it gets the data
889
    resp = get_app(pub).get(sign_uri('/api/forms/test/ods', user=local_user))
890
    assert resp.content_type == 'application/vnd.oasis.opendocument.spreadsheet'
891

  
892
    # check it still gives a ods file when there is more data
893
    for i in range(300):
894
        formdata = data_class()
895
        formdata.data = data
896
        formdata.user_id = local_user.id
897
        formdata.just_created()
898
        formdata.jump_status('new')
899
        formdata.store()
900

  
901
    resp = get_app(pub).get(sign_uri('/api/forms/test/ods', user=local_user))
902
    assert resp.content_type == 'application/vnd.oasis.opendocument.spreadsheet'
903
    zipf = zipfile.ZipFile(BytesIO(resp.body))
904
    ods_sheet = ET.parse(zipf.open('content.xml'))
905
    assert len(ods_sheet.findall('.//{%s}table-row' % ods.NS['table'])) == 311
906

  
907

  
908
def test_api_global_geojson(pub, local_user):
909
    Role.wipe()
910
    role = Role(name='test')
911
    role.store()
912

  
913
    FormDef.wipe()
914
    formdef = FormDef()
915
    formdef.name = 'test'
916
    formdef.workflow_roles = {'_receiver': role.id}
917
    formdef.fields = []
918
    formdef.store()
919

  
920
    data_class = formdef.data_class()
921
    data_class.wipe()
922

  
923
    formdef.geolocations = {'base': 'Location'}
924
    formdef.store()
925

  
926
    for i in range(30):
927
        formdata = data_class()
928
        formdata.geolocations = {'base': {'lat': 48, 'lon': 2}}
929
        formdata.user_id = local_user.id
930
        formdata.just_created()
931
        if i % 3 == 0:
932
            formdata.jump_status('new')
933
        else:
934
            formdata.jump_status('finished')
935
        formdata.store()
936

  
937
    if not pub.is_using_postgresql():
938
        resp = get_app(pub).get(sign_uri('/api/forms/geojson', user=local_user), status=404)
939
        pytest.skip('this requires SQL')
940
        return
941

  
942
    # check empty content if user doesn't have the appropriate role
943
    resp = get_app(pub).get(sign_uri('/api/forms/geojson', user=local_user))
944
    assert 'features' in resp.json
945
    assert len(resp.json['features']) == 0
946

  
947
    # add proper role to user
948
    local_user.roles = [role.id]
949
    local_user.store()
950

  
951
    # check it gets the data
952
    resp = get_app(pub).get(sign_uri('/api/forms/geojson', user=local_user))
953
    assert 'features' in resp.json
954
    assert len(resp.json['features']) == 10
955

  
956
    # check with a filter
957
    resp = get_app(pub).get(sign_uri('/api/forms/geojson?status=done', user=local_user))
958
    assert 'features' in resp.json
959
    assert len(resp.json['features']) == 20
960

  
961

  
962
def test_api_global_listing(pub, local_user):
963
    if not pub.is_using_postgresql():
964
        resp = get_app(pub).get(sign_uri('/api/forms/geojson', user=local_user), status=404)
965
        pytest.skip('this requires SQL')
966
        return
967

  
968
    Role.wipe()
969
    role = Role(name='test')
970
    role.store()
971

  
972
    # check there's no crash if there are no formdefs
973
    resp = get_app(pub).get(sign_uri('/api/forms/', user=local_user))
974
    assert len(resp.json['data']) == 0
975

  
976
    FormDef.wipe()
977
    formdef = FormDef()
978
    formdef.name = 'test'
979
    formdef.workflow_roles = {'_receiver': role.id}
980
    formdef.fields = [
981
        fields.StringField(id='0', label='foobar', varname='foobar'),
982
    ]
983
    formdef.store()
984

  
985
    data_class = formdef.data_class()
986
    data_class.wipe()
987

  
988
    formdef.store()
989

  
990
    for i in range(30):
991
        formdata = data_class()
992
        formdata.data = {'0': 'FOO BAR'}
993
        formdata.user_id = local_user.id
994
        formdata.just_created()
995
        if i % 3 == 0:
996
            formdata.jump_status('new')
997
        else:
998
            formdata.jump_status('finished')
999
        formdata.store()
1000

  
1001
    # check empty content if user doesn't have the appropriate role
1002
    resp = get_app(pub).get(sign_uri('/api/forms/', user=local_user))
1003
    assert len(resp.json['data']) == 0
1004

  
1005
    # add proper role to user
1006
    local_user.roles = [role.id]
1007
    local_user.store()
1008

  
1009
    # check it gets the data
1010
    resp = get_app(pub).get(sign_uri('/api/forms/', user=local_user))
1011
    assert len(resp.json['data']) == 10
1012

  
1013
    # check with a filter
1014
    resp = get_app(pub).get(sign_uri('/api/forms/?status=done', user=local_user))
1015
    assert len(resp.json['data']) == 20
1016

  
1017
    # check limit/offset
1018
    resp = get_app(pub).get(sign_uri('/api/forms/?status=done&limit=5', user=local_user))
1019
    assert len(resp.json['data']) == 5
1020
    resp = get_app(pub).get(sign_uri('/api/forms/?status=done&offset=5&limit=5', user=local_user))
1021
    assert len(resp.json['data']) == 5
1022
    resp = get_app(pub).get(sign_uri('/api/forms/?status=done&offset=18&limit=5', user=local_user))
1023
    assert len(resp.json['data']) == 2
1024

  
1025
    # check error handling
1026
    get_app(pub).get(sign_uri('/api/forms/?status=done&limit=plop', user=local_user), status=400)
1027
    get_app(pub).get(sign_uri('/api/forms/?status=done&offset=plop', user=local_user), status=400)
1028
    get_app(pub).get(sign_uri('/api/forms/?category_id=plop', user=local_user), status=400)
1029

  
1030
    # check when there are missing statuses
1031
    for formdata in data_class.select():
1032
        formdata.status = 'wf-missing'
1033
        formdata.store()
1034
    resp = get_app(pub).get(sign_uri('/api/forms/?status=all', user=local_user))
1035
    assert resp.json['data'][0]['status'] is None
1036
    assert 'unknown' in resp.json['data'][0]['title']
1037

  
1038

  
1039
def test_api_global_listing_ignored_roles(pub, local_user):
1040
    test_api_global_listing(pub, local_user)
1041

  
1042
    role = Role(name='test2')
1043
    role.store()
1044

  
1045
    formdef = FormDef()
1046
    formdef.name = 'test2'
1047
    formdef.workflow_roles = {'_receiver': role.id}
1048
    formdef.fields = [
1049
        fields.StringField(id='0', label='foobar', varname='foobar'),
1050
    ]
1051
    formdef.store()
1052

  
1053
    data_class = formdef.data_class()
1054
    data_class.wipe()
1055

  
1056
    for i in range(10):
1057
        formdata = data_class()
1058
        formdata.data = {'0': 'FOO BAR'}
1059
        formdata.user_id = local_user.id
1060
        formdata.just_created()
1061
        formdata.jump_status('new')
1062
        formdata.store()
1063

  
1064
    # considering roles
1065
    resp = get_app(pub).get(sign_uri('/api/forms/?status=all&limit=100', user=local_user))
1066
    assert len(resp.json['data']) == 30
1067

  
1068
    # ignore roles
1069
    resp = get_app(pub).get(sign_uri('/api/forms/?status=all&limit=100&ignore-roles=on', user=local_user))
1070
    assert len(resp.json['data']) == 40
1071

  
1072
    # check sensitive forms are not exposed
1073
    formdef.skip_from_360_view = True
1074
    formdef.store()
1075
    resp = get_app(pub).get(sign_uri('/api/forms/?status=all&limit=100&ignore-roles=on', user=local_user))
1076
    assert len(resp.json['data']) == 30
1077

  
1078

  
1079
def test_api_include_anonymised(pub, local_user):
1080
    if not pub.is_using_postgresql():
1081
        resp = get_app(pub).get(sign_uri('/api/forms/geojson', user=local_user), status=404)
1082
        pytest.skip('this requires SQL')
1083
        return
1084

  
1085
    Role.wipe()
1086
    role = Role(name='test')
1087
    role.store()
1088

  
1089
    # add proper role to user
1090
    local_user.roles = [role.id]
1091
    local_user.store()
1092

  
1093
    FormDef.wipe()
1094
    formdef = FormDef()
1095
    formdef.name = 'test'
1096
    formdef.workflow_roles = {'_receiver': role.id}
1097
    formdef.fields = [
1098
        fields.StringField(id='0', label='foobar', varname='foobar'),
1099
    ]
1100
    formdef.store()
1101

  
1102
    data_class = formdef.data_class()
1103
    data_class.wipe()
1104

  
1105
    for i in range(10):
1106
        formdata = data_class()
1107
        formdata.data = {'0': 'FOO BAR'}
1108
        formdata.user_id = local_user.id
1109
        formdata.just_created()
1110
        formdata.jump_status('new')
1111
        formdata.store()
1112

  
1113
    # anonymise the last one
1114
    formdata.anonymise()
1115

  
1116
    resp = get_app(pub).get(sign_uri('/api/forms/', user=local_user))
1117
    assert len(resp.json['data']) == 10
1118

  
1119
    resp = get_app(pub).get(sign_uri('/api/forms/?include-anonymised=on', user=local_user))
1120
    assert len(resp.json['data']) == 10
1121

  
1122
    resp = get_app(pub).get(sign_uri('/api/forms/?include-anonymised=off', user=local_user))
1123
    assert len(resp.json['data']) == 9
1124

  
1125

  
1126
def test_api_ics_formdata(pub, local_user, ics_data):
1127
    role = Role.select()[0]
1128

  
1129
    # check access is denied if the user has not the appropriate role
1130
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar', user=local_user), status=403)
1131
    # even if there's an anonymse parameter
1132
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar?anonymise', user=local_user), status=403)
1133

  
1134
    # add proper role to user
1135
    local_user.roles = [role.id]
1136
    local_user.store()
1137

  
1138
    def remove_dtstamp(body):
1139
        # remove dtstamp as the precise timing may vary between two consecutive
1140
        # calls and we shouldn't care.
1141
        return re.sub('DTSTAMP:.*', 'DTSTAMP:--', body)
1142

  
1143
    # check 404 on incomplete ics url access
1144
    assert get_app(pub).get(sign_uri('/api/forms/test/ics/', user=local_user), status=404)
1145

  
1146
    # check it gets the data
1147
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar', user=local_user))
1148
    resp2 = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar/', user=local_user))
1149
    assert remove_dtstamp(resp.text) == remove_dtstamp(resp2.text)
1150
    assert resp.headers['content-type'] == 'text/calendar; charset=utf-8'
1151
    assert resp.text.count('BEGIN:VEVENT') == 10
1152
    # check that description contains form name, display id, workflow status,
1153
    # backoffice url and attached user
1154
    pattern = re.compile(r'DESCRIPTION:testé \| 1-\d+ \| New', re.MULTILINE)
1155
    m = pattern.findall(resp.text)
1156
    assert len(m) == 10
1157
    assert resp.text.count('Jean Darmette') == 10
1158
    assert resp.text.count('DTSTART') == 10
1159

  
1160
    # check formdata digest summary and description contains the formdata digest
1161
    pattern = re.compile(r'SUMMARY:testé #1-\d+ - plöp \d{4}-\d{2}-\d{2} \d{2}:\d{2} plÔp', re.MULTILINE)
1162
    m = pattern.findall(resp.text)
1163
    assert len(m) == 10
1164
    assert resp.text.count(r'plöp') == 20
1165

  
1166
    # check with a filter
1167
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar?filter=done', user=local_user))
1168
    assert resp.text.count('BEGIN:VEVENT') == 20
1169
    pattern = re.compile(r'DESCRIPTION:testé \| 1-\d+ \| Finished', re.MULTILINE)
1170
    m = pattern.findall(resp.text)
1171
    assert len(m) == 20
1172
    assert resp.text.count('Jean Darmette') == 20
1173

  
1174
    # check 404 on erroneous field var
1175
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/xxx', user=local_user), status=404)
1176

  
1177
    # check 404 on an erroneous field var for endtime
1178
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar/xxx', user=local_user), status=404)
1179

  
1180
    # check 404 on too many path elements
1181
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar/foobar2/xxx', user=local_user), status=404)
1182

  
1183
    # check ics data with start and end varnames
1184
    resp = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar/foobar2', user=local_user))
1185
    resp2 = get_app(pub).get(sign_uri('/api/forms/test/ics/foobar/foobar2/', user=local_user))
1186
    assert remove_dtstamp(resp.text) == remove_dtstamp(resp2.text)
1187
    assert resp.text.count('DTSTART') == 10
1188
    assert resp.text.count('DTEND') == 10
1189

  
1190

  
1191
def test_api_ics_formdata_http_auth(pub, local_user, admin_user, ics_data):
1192
    role = Role.select()[0]
1193

  
1194
    # check as admin
1195
    app = login(get_app(pub))
1196
    resp = app.get('/api/forms/test/ics/foobar', status=200)
1197

  
1198
    # no access
1199
    app = get_app(pub)
1200
    resp = app.get('/api/forms/test/ics/foobar?email=%s' % local_user.email, status=401)
1201
    assert resp.headers['Www-Authenticate']
1202

  
1203
    # auth but no access
1204
    app = get_app(pub)
1205
    app.authorization = ('Basic', ('user', 'password'))
1206
    resp = app.get('/api/forms/test/ics/foobar?email=%s' % local_user.email, status=401)
1207

  
1208
    # add authentication info
1209
    pub.load_site_options()
1210
    pub.site_options.add_section('api-http-auth-ics')
1211
    pub.site_options.set('api-http-auth-ics', 'user', 'password')
1212
    pub.site_options.write(open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w'))
1213

  
1214
    # check access is denied if the user has not the appropriate role
1215
    resp = app.get('/api/forms/test/ics/foobar?email=%s' % local_user.email, status=403)
1216

  
1217
    # check access is denied if the user is not specified
1218
    resp = app.get('/api/forms/test/ics/foobar', status=403)
1219

  
1220
    # add proper role to user
1221
    local_user.roles = [role.id]
1222
    local_user.store()
1223

  
1224
    # check it gets the data
1225
    resp = app.get('/api/forms/test/ics/foobar?email=%s' % local_user.email, status=200)
1226
    assert resp.headers['content-type'] == 'text/calendar; charset=utf-8'
1227
    assert resp.text.count('BEGIN:VEVENT') == 10
1228

  
1229
    # check it fails with a different password
1230
    app.authorization = ('Basic', ('user', 'password2'))
1231
    resp = app.get('/api/forms/test/ics/foobar?email=%s' % local_user.email, status=401)
1232

  
1233

  
1234
def test_api_ics_formdata_custom_view(pub, local_user, ics_data):
1235
    role = Role.select()[0]
1236

  
1237
    formdef = FormDef.get_by_urlname('test')
1238

  
1239
    pub.custom_view_class.wipe()
1240
    custom_view = pub.custom_view_class()
1241
    custom_view.title = 'custom view'
1242
    custom_view.formdef = formdef
1243
    custom_view.columns = {'list': [{'id': '0'}]}
1244
    custom_view.filters = {}
1245
    custom_view.visibility = 'any'
1246
    custom_view.store()
1247

  
1248
    # check access is denied if the user has not the appropriate role
1249
    resp = get_app(pub).get(sign_uri('/api/forms/test/custom-view/ics/foobar', user=local_user), status=403)
1250
    # even if there's an anonymise parameter
1251
    resp = get_app(pub).get(
1252
        sign_uri('/api/forms/test/custom-view/ics/foobar?anonymise', user=local_user), status=403
1253
    )
1254

  
1255
    # add proper role to user
1256
    local_user.roles = [role.id]
1257
    local_user.store()
1258

  
1259
    def remove_dtstamp(body):
1260
        # remove dtstamp as the precise timing may vary between two consecutive
1261
        # calls and we shouldn't care.
1262
        return re.sub('DTSTAMP:.*', 'DTSTAMP:--', body)
1263

  
1264
    # check it gets the data
1265
    resp = get_app(pub).get(sign_uri('/api/forms/test/custom-view/ics/foobar', user=local_user))
1266
    resp2 = get_app(pub).get(sign_uri('/api/forms/test/custom-view/ics/foobar/', user=local_user))
1267
    assert remove_dtstamp(resp.text) == remove_dtstamp(resp2.text)
1268
    assert resp.headers['content-type'] == 'text/calendar; charset=utf-8'
1269
    assert resp.text.count('BEGIN:VEVENT') == 10
1270

  
1271
    # check ics data with start and end varnames
1272
    resp = get_app(pub).get(sign_uri('/api/forms/test/custom-view/ics/foobar/foobar2', user=local_user))
1273
    resp2 = get_app(pub).get(sign_uri('/api/forms/test/custom-view/ics/foobar/foobar2/', user=local_user))
1274
    assert remove_dtstamp(resp.text) == remove_dtstamp(resp2.text)
1275
    assert resp.text.count('DTSTART') == 10
1276
    assert resp.text.count('DTEND') == 10
1277

  
1278

  
1279
def test_api_invalid_http_basic_auth(pub, local_user, admin_user, ics_data):
1280
    app = get_app(pub)
1281
    app.get(
1282
        '/api/forms/test/ics/foobar?email=%s' % local_user.email,
1283
        headers={'Authorization': 'Basic garbage'},
1284
        status=401,
1285
    )
tests/api/test_formdef.py
1
# -*- coding: utf-8 -*-
2

  
3
import base64
4
import datetime
5
import json
6
import os
7
import time
8

  
9
import mock
10
import pytest
11
from django.utils.encoding import force_text
12
from django.utils.six import StringIO
13
from django.utils.six.moves.urllib import parse as urllib
14
from quixote import get_publisher
15
from utilities import clean_temporary_pub, create_temporary_pub, get_app
16

  
17
from wcs import fields, qommon
18
from wcs.api_utils import sign_url
19
from wcs.data_sources import NamedDataSource
20
from wcs.formdef import FormDef
21
from wcs.qommon.form import PicklableUpload
22
from wcs.qommon.http_request import HTTPRequest
23
from wcs.roles import Role
24
from wcs.wf.jump import JumpWorkflowStatusItem
25
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef
26

  
27
from .utils import sign_uri
28

  
29

  
30
def pytest_generate_tests(metafunc):
31
    if 'pub' in metafunc.fixturenames:
32
        metafunc.parametrize('pub', ['pickle', 'sql'], indirect=True)
33

  
34

  
35
@pytest.fixture
36
def pub(request, emails):
37
    pub = create_temporary_pub(sql_mode=(request.param == 'sql'))
38

  
39
    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
40
    pub.set_app_dir(req)
41
    pub.cfg['identification'] = {'methods': ['password']}
42
    pub.cfg['language'] = {'language': 'en'}
43
    pub.write_cfg()
44

  
45
    open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w').write(
46
        '''\
47
[api-secrets]
48
coucou = 1234
49
'''
50
    )
51

  
52
    return pub
53

  
54

  
55
def teardown_module(module):
56
    clean_temporary_pub()
57

  
58

  
59
@pytest.fixture
60
def local_user():
61
    get_publisher().user_class.wipe()
62
    user = get_publisher().user_class()
63
    user.name = 'Jean Darmette'
64
    user.email = 'jean.darmette@triffouilis.fr'
65
    user.name_identifiers = ['0123456789']
66
    user.store()
67
    return user
68

  
69

  
70
def test_formdef_list(pub):
71
    Role.wipe()
72
    role = Role(name='Foo bar')
73
    role.id = '14'
74
    role.store()
75

  
76
    FormDef.wipe()
77
    formdef = FormDef()
78
    formdef.name = 'test'
79
    formdef.description = 'plop'
80
    formdef.keywords = 'mobile, test'
81
    formdef.workflow_roles = {'_receiver': str(role.id)}
82
    formdef.fields = []
83
    formdef.store()
84

  
85
    # anonymous access -> 403
86
    resp1 = get_app(pub).get('/json', status=403)
87
    resp2 = get_app(pub).get('/', headers={'Accept': 'application/json'}, status=403)
88
    resp3 = get_app(pub).get('/api/formdefs/', status=403)
89

  
90
    # signed request
91
    resp1 = get_app(pub).get(sign_uri('/json'))
92
    resp2 = get_app(pub).get(sign_uri('/'), headers={'Accept': 'application/json'})
93
    resp3 = get_app(pub).get(sign_uri('/api/formdefs/'))
94
    assert resp1.json == resp2.json == resp3.json
95
    assert resp1.json['data'][0]['title'] == 'test'
96
    assert resp1.json['data'][0]['url'] == 'http://example.net/test/'
97
    assert resp1.json['data'][0]['redirection'] is False
98
    assert resp1.json['data'][0]['always_advertise'] is False
99
    assert resp1.json['data'][0]['description'] == 'plop'
100
    assert resp1.json['data'][0]['keywords'] == ['mobile', 'test']
101
    assert list(resp1.json['data'][0]['functions'].keys()) == ['_receiver']
102
    assert resp1.json['data'][0]['functions']['_receiver']['label'] == 'Recipient'
103
    assert resp1.json['data'][0]['functions']['_receiver']['role']['slug'] == role.slug
104
    assert resp1.json['data'][0]['functions']['_receiver']['role']['name'] == role.name
105
    assert 'count' not in resp1.json['data'][0]
106

  
107
    # backoffice_submission formdef : none
108
    resp1 = get_app(pub).get('/api/formdefs/?backoffice-submission=on', status=403)
109
    resp1 = get_app(pub).get(sign_uri('/api/formdefs/?backoffice-submission=on'))
110
    assert resp1.json['err'] == 0
111
    assert len(resp1.json['data']) == 0
112

  
113
    formdef.data_class().wipe()
114

  
115
    # a draft
116
    formdata = formdef.data_class()()
117
    formdata.data = {}
118
    formdata.just_created()
119
    formdata.status = 'draft'
120
    formdata.store()
121

  
122
    other_formdef = FormDef()
123
    other_formdef.name = 'test 2'
124
    other_formdef.fields = []
125
    other_formdef.store()
126
    other_formdata = other_formdef.data_class()()
127
    other_formdata.data = {}
128
    other_formdata.just_created()
129
    other_formdata.store()
130

  
131
    # formdata created:
132
    # - 1 day ago (=3*4)
133
    # - 7 days ago (=2*2)
134
    # - 29 days ago (=1*1)
135
    # - 31 days ago (=0)
136
    for days in [1, 1, 1, 7, 7, 29, 31]:
137
        formdata = formdef.data_class()()
138
        formdata.data = {}
139
        formdata.just_created()
140
        formdata.receipt_time = (datetime.datetime.now() - datetime.timedelta(days=days)).timetuple()
141
        formdata.store()
142

  
143
    resp = get_app(pub).get(sign_uri('/api/formdefs/?include-count=on'))
144
    if not pub.is_using_postgresql():
145
        assert resp.json['data'][0]['count'] == 8
146
    else:
147
        # 3*4 + 2*2 + 1*1
148
        assert resp.json['data'][0]['count'] == 17
149

  
150

  
151
def test_limited_formdef_list(pub, local_user):
152
    Role.wipe()
153
    role = Role(name='Foo bar')
154
    role.id = '14'
155
    role.store()
156

  
157
    FormDef.wipe()
158
    formdef = FormDef()
159
    formdef.name = 'test'
160
    formdef.description = 'plop'
161
    formdef.workflow_roles = {'_receiver': str(role.id)}
162
    formdef.fields = []
163
    formdef.store()
164

  
165
    resp = get_app(pub).get(sign_uri('/api/formdefs/'))
166
    assert resp.json['err'] == 0
167
    assert len(resp.json['data']) == 1
168
    assert resp.json['data'][0]['authentication_required'] is False
169
    # not present in backoffice-submission formdefs
170
    resp = get_app(pub).get(sign_uri('/api/formdefs/?backoffice-submission=on'))
171
    assert resp.json['err'] == 0
172
    assert len(resp.json['data']) == 0
173

  
174
    # check it's not advertised
175
    formdef.roles = [role.id]
176
    formdef.store()
177
    resp = get_app(pub).get(sign_uri('/api/formdefs/'))
178
    resp2 = get_app(pub).get(sign_uri('/api/formdefs/?NameID='))
179
    resp3 = get_app(pub).get(sign_uri('/api/formdefs/?NameID=XXX'))
180
    resp4 = get_app(pub).get(sign_uri('/api/formdefs/?NameID=%s' % local_user.name_identifiers[0]))
181
    assert resp.json['err'] == 0
182
    assert len(resp.json['data']) == 1  # advertised in naked calls (as done from combo)
183
    assert len(resp2.json['data']) == 0  # not advertised otherwise
184
    assert resp2.json == resp3.json == resp4.json
185
    # still not present in backoffice-submission formdefs
186
    resp = get_app(pub).get(sign_uri('/api/formdefs/?backoffice-submission=on'))
187
    assert resp.json['err'] == 0
188
    assert len(resp.json['data']) == 0
189

  
190
    # unless user has correct roles
191
    local_user.roles = [role.id]
192
    local_user.store()
193
    resp = get_app(pub).get(sign_uri('/api/formdefs/?NameID=%s' % local_user.name_identifiers[0]))
194
    assert resp.json['err'] == 0
195
    assert len(resp.json['data']) == 1
196

  
197
    local_user.roles = []
198
    local_user.store()
199

  
200
    # check it's also included in anonymous/signed calls, but marked for
201
    # authentication
202
    resp = get_app(pub).get(sign_uri('/api/formdefs/'))
203
    assert resp.json['data'][0]
204
    assert resp.json['data'][0]['authentication_required'] is True
205

  
206
    # check it's advertised
207
    formdef.always_advertise = True
208
    formdef.store()
209
    resp = get_app(pub).get(sign_uri('/api/formdefs/'))
210
    resp2 = get_app(pub).get(sign_uri('/api/formdefs/?NameID='))
211
    resp3 = get_app(pub).get(sign_uri('/api/formdefs/?NameID=XXX'))
212
    resp4 = get_app(pub).get(sign_uri('/api/formdefs/?NameID=%s' % local_user.name_identifiers[0]))
213
    assert resp.json['err'] == 0
214
    assert len(resp.json['data']) == 1
215
    assert resp.json['data'][0]['authentication_required']
216
    assert resp.json == resp2.json == resp3.json == resp4.json
217

  
218
    formdef.required_authentication_contexts = ['fedict']
219
    formdef.store()
220
    resp = get_app(pub).get(sign_uri('/api/formdefs/'))
221
    assert resp.json['data'][0]['required_authentication_contexts'] == ['fedict']
222

  
223

  
224
def test_formdef_list_redirection(pub):
225
    FormDef.wipe()
226
    formdef = FormDef()
227
    formdef.name = 'test'
228
    formdef.disabled = True
229
    formdef.disabled_redirection = 'http://example.net'
230
    formdef.fields = []
231
    formdef.store()
232

  
233
    resp1 = get_app(pub).get(sign_uri('/json'))
234
    assert resp1.json['err'] == 0
235
    assert resp1.json['data'][0]['title'] == 'test'
236
    assert resp1.json['data'][0]['url'] == 'http://example.net/test/'
237
    assert resp1.json['data'][0]['redirection'] is True
238
    assert 'count' not in resp1.json['data'][0]
239

  
240

  
241
def test_backoffice_submission_formdef_list(pub, local_user):
242
    Role.wipe()
243
    role = Role(name='Foo bar')
244
    role.id = '14'
245
    role.store()
246

  
247
    FormDef.wipe()
248
    formdef = FormDef()
249
    formdef.name = 'test'
250
    formdef.description = 'plop'
251
    formdef.workflow_roles = {'_receiver': str(role.id)}
252
    formdef.fields = []
253
    formdef.store()
254

  
255
    formdef2 = FormDef()
256
    formdef2.name = 'ignore me'
257
    formdef2.fields = []
258
    formdef2.store()
259

  
260
    resp = get_app(pub).get(sign_uri('/api/formdefs/?backoffice-submission=on'))
261
    assert resp.json['err'] == 0
262
    assert len(resp.json['data']) == 0
263

  
264
    # check it's not advertised ...
265
    formdef.backoffice_submission_roles = [role.id]
266
    formdef.store()
267
    resp = get_app(pub).get(sign_uri('/api/formdefs/?backoffice-submission=on'))
268
    assert resp.json['err'] == 0
269
    assert len(resp.json['data']) == 0
270

  
271
    # even if it's advertised on frontoffice
272
    formdef.always_advertise = True
273
    formdef.store()
274
    resp = get_app(pub).get(sign_uri('/api/formdefs/?backoffice-submission=on'))
275
    assert resp.json['err'] == 0
276
    assert len(resp.json['data']) == 0
277

  
278
    # even if user is admin
279
    local_user.is_admin = True
280
    local_user.store()
281
    resp = get_app(pub).get(
282
        sign_uri('/api/formdefs/?backoffice-submission=on&NameID=%s' % local_user.name_identifiers[0])
283
    )
284
    assert resp.json['err'] == 0
285
    assert len(resp.json['data']) == 0
286
    local_user.is_admin = False
287
    local_user.store()
288

  
289
    # ... unless user has correct roles
290
    local_user.roles = [role.id]
291
    local_user.store()
292
    resp = get_app(pub).get(
293
        sign_uri('/api/formdefs/?backoffice-submission=on&NameID=%s' % local_user.name_identifiers[0])
294
    )
295
    assert resp.json['err'] == 0
296
    assert len(resp.json['data']) == 1
297
    assert 'backoffice_submission_url' in resp.json['data'][0]
298

  
299
    # but not advertised if it's a redirection
300
    formdef.disabled = True
301
    formdef.disabled_redirection = 'http://example.net'
302
    formdef.store()
303
    resp = get_app(pub).get(sign_uri('/api/formdefs/?backoffice-submission=on'))
304
    assert resp.json['err'] == 0
305
    assert len(resp.json['data']) == 0
306

  
307

  
308
def test_formdef_schema(pub):
309
    Workflow.wipe()
310
    workflow = Workflow(name='test')
311
    st1 = workflow.add_status('Status1', 'st1')
312
    jump = JumpWorkflowStatusItem()
313
    jump.status = 'st2'
314
    jump.timeout = 100
315
    st1.items.append(jump)
316
    st2 = workflow.add_status('Status2', 'st2')
317
    jump = JumpWorkflowStatusItem()
318
    jump.status = 'st3'
319
    st2.items.append(jump)
320
    st2 = workflow.add_status('Status3', 'st3')
321
    workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
322
    workflow.backoffice_fields_formdef.fields = [
323
        fields.StringField(id='bo1', label='1st backoffice field', type='string', varname='backoffice_blah'),
324
    ]
325
    workflow.store()
326
    FormDef.wipe()
327
    formdef = FormDef()
328
    formdef.name = 'test'
329
    formdef.fields = [
330
        fields.StringField(id='0', label='foobar'),
331
        fields.ItemField(
332
            id='1',
333
            label='foobar1',
334
            varname='foobar1',
335
            data_source={
336
                'type': 'json',
337
                'value': 'http://datasource.com',
338
            },
339
        ),
340
        fields.ItemsField(
341
            id='2',
342
            label='foobar2',
343
            varname='foobar2',
344
            data_source={
345
                'type': 'formula',
346
                'value': '[dict(id=i, text=\'label %s\' % i, foo=i) for i in range(10)]',
347
            },
348
        ),
349
    ]
350

  
351
    formdef.workflow_id = workflow.id
352
    formdef.store()
353

  
354
    with mock.patch('wcs.qommon.misc.urlopen') as urlopen:
355
        urlopen.side_effect = lambda *args: StringIO(
356
            '''\
357
{"data": [{"id": 0, "text": "zéro", "foo": "bar"}, \
358
{"id": 1, "text": "uné", "foo": "bar1"}, \
359
{"id": 2, "text": "deux", "foo": "bar2"}]}'''
360
        )
361
        resp = get_app(pub).get('/api/formdefs/test/schema')
362
        resp2 = get_app(pub).get('/test/schema')
363
        resp3 = get_app(pub).get(sign_url('/api/formdefs/test/schema?orig=coucou', '1234'))
364
    resp4 = get_app(pub).get(sign_url('/api/formdefs/test/schema?orig=coucou', '1234'))
365

  
366
    # check schema
367
    assert resp.json == resp2.json
368
    assert set(resp.json.keys()) >= set(
369
        [
370
            'enable_tracking_codes',
371
            'url_name',
372
            'description',
373
            'workflow',
374
            'expiration_date',
375
            'discussion',
376
            'has_captcha',
377
            'always_advertise',
378
            'name',
379
            'disabled',
380
            'only_allow_one',
381
            'fields',
382
            'keywords',
383
            'publication_date',
384
            'detailed_emails',
385
            'disabled_redirection',
386
        ]
387
    )
388
    assert resp.json['name'] == 'test'
389

  
390
    # fields checks
391
    assert resp.json['fields'][0]['label'] == 'foobar'
392
    assert resp.json['fields'][0]['type'] == 'string'
393

  
394
    assert resp.json['fields'][1]['label'] == 'foobar1'
395
    assert resp.json['fields'][1]['type'] == 'item'
396

  
397
    # check structured items are only exported for authenticated callers
398
    assert resp.json['fields'][1]['items'] == []
399
    assert resp.json['fields'][2]['items'] == []
400
    assert 'structured_items' not in resp.json['fields'][1]
401
    assert 'structured_items' not in resp.json['fields'][2]
402

  
403
    assert len(resp3.json['fields'][1]['structured_items']) == 3
404
    assert resp3.json['fields'][1]['structured_items'][0]['id'] == 0
405
    assert resp3.json['fields'][1]['structured_items'][0]['text'] == u'zéro'
406
    assert resp3.json['fields'][1]['structured_items'][0]['foo'] == 'bar'
407
    assert resp3.json['fields'][1]['items'][0] == u'zéro'
408

  
409
    assert resp3.json['fields'][2]['label'] == 'foobar2'
410
    assert resp3.json['fields'][2]['type'] == 'items'
411
    assert len(resp3.json['fields'][2]['structured_items']) == 10
412
    assert resp3.json['fields'][2]['structured_items'][0]['id'] == 0
413
    assert resp3.json['fields'][2]['structured_items'][0]['text'] == 'label 0'
414
    assert resp3.json['fields'][2]['structured_items'][0]['foo'] == 0
415
    assert resp3.json['fields'][2]['items'][0] == 'label 0'
416

  
417
    # if structured_items fails no values
418
    assert 'structured_items' not in resp4.json['fields'][1]
419
    assert resp4.json['fields'][1]['items'] == []
420

  
421
    # workflow checks
422
    assert len(resp.json['workflow']['statuses']) == 3
423
    assert resp.json['workflow']['statuses'][0]['id'] == 'st1'
424
    assert resp.json['workflow']['statuses'][0]['endpoint'] is False
425
    assert resp.json['workflow']['statuses'][0]['waitpoint'] is True
426
    assert resp.json['workflow']['statuses'][1]['id'] == 'st2'
427
    assert resp.json['workflow']['statuses'][1]['endpoint'] is False
428
    assert resp.json['workflow']['statuses'][1]['waitpoint'] is False
429
    assert resp.json['workflow']['statuses'][2]['id'] == 'st3'
430
    assert resp.json['workflow']['statuses'][2]['endpoint'] is True
431
    assert resp.json['workflow']['statuses'][2]['waitpoint'] is True
432
    assert len(resp.json['workflow']['fields']) == 1
433

  
434
    assert resp.json['workflow']['fields'][0]['label'] == '1st backoffice field'
435

  
436
    get_app(pub).get('/api/formdefs/xxx/schema', status=404)
437

  
438

  
439
def test_post_invalid_json(pub, local_user):
440
    resp = get_app(pub).post(
441
        '/api/formdefs/test/submit', params='not a json payload', content_type='application/json', status=400
442
    )
443
    assert resp.json['err'] == 1
444
    assert resp.json['err_class'] == 'Invalid request'
445

  
446

  
447
def test_formdef_submit(pub, local_user):
448
    Role.wipe()
449
    role = Role(name='test')
450
    role.store()
451
    local_user.roles = [role.id]
452
    local_user.store()
453

  
454
    FormDef.wipe()
455
    formdef = FormDef()
456
    formdef.name = 'test'
457
    formdef.fields = [fields.StringField(id='0', label='foobar')]
458
    formdef.store()
459
    data_class = formdef.data_class()
460

  
461
    resp = get_app(pub).post_json('/api/formdefs/test/submit', {'data': {}}, status=403)
462
    assert resp.json['err'] == 1
463
    assert resp.json['err_desc'] == 'unsigned API call'
464

  
465
    def url():
466
        signed_url = sign_url(
467
            'http://example.net/api/formdefs/test/submit'
468
            + '?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
469
            '1234',
470
        )
471
        return signed_url[len('http://example.net') :]
472

  
473
    resp = get_app(pub).post_json(url(), {'data': {}})
474
    assert resp.json['err'] == 0
475
    assert resp.json['data']['url'] == ('http://example.net/test/%s/' % resp.json['data']['id'])
476
    assert resp.json['data']['backoffice_url'] == (
477
        'http://example.net/backoffice/management/test/%s/' % resp.json['data']['id']
478
    )
479
    assert resp.json['data']['api_url'] == ('http://example.net/api/forms/test/%s/' % resp.json['data']['id'])
480
    assert data_class.get(resp.json['data']['id']).status == 'wf-new'
481
    assert data_class.get(resp.json['data']['id']).user_id == str(local_user.id)
482
    assert data_class.get(resp.json['data']['id']).tracking_code is None
483

  
484
    local_user2 = get_publisher().user_class()
485
    local_user2.name = 'Test'
486
    local_user2.email = 'foo@localhost'
487
    local_user2.store()
488
    resp = get_app(pub).post_json(url(), {'data': {}, 'user': {'NameID': [], 'email': local_user2.email}})
489
    assert data_class.get(resp.json['data']['id']).user.email == local_user2.email
490

  
491
    resp = get_app(pub).post(
492
        url(), json.dumps({'data': {}}), status=400
493
    )  # missing Content-Type: application/json header
494
    assert resp.json['err_desc'] == 'expected JSON but missing appropriate content-type'
495

  
496
    # check qualified content type are recognized
497
    resp = get_app(pub).post(url(), json.dumps({'data': {}}), content_type='application/json; charset=utf-8')
498
    assert resp.json['data']['url']
499

  
500
    formdef.disabled = True
501
    formdef.store()
502
    resp = get_app(pub).post_json(url(), {'data': {}}, status=403)
503
    assert resp.json['err'] == 1
504
    assert resp.json['err_desc'] == 'disabled form'
505

  
506
    formdef.disabled = False
507
    formdef.store()
508
    resp = get_app(pub).post_json(url(), {'meta': {'backoffice-submission': True}, 'data': {}}, status=403)
509
    formdef.backoffice_submission_roles = ['xx']
510
    formdef.store()
511
    resp = get_app(pub).post_json(url(), {'meta': {'backoffice-submission': True}, 'data': {}}, status=403)
512
    formdef.backoffice_submission_roles = [role.id]
513
    formdef.store()
514
    resp = get_app(pub).post_json(url(), {'meta': {'backoffice-submission': True}, 'data': {}})
515
    assert data_class.get(resp.json['data']['id']).status == 'wf-new'
516
    assert data_class.get(resp.json['data']['id']).backoffice_submission is True
517
    assert data_class.get(resp.json['data']['id']).user_id is None
518
    assert data_class.get(resp.json['data']['id']).submission_agent_id == str(local_user.id)
519

  
520
    formdef.enable_tracking_codes = True
521
    formdef.store()
522
    resp = get_app(pub).post_json(url(), {'data': {}})
523
    assert data_class.get(resp.json['data']['id']).tracking_code
524

  
525
    resp = get_app(pub).post_json(url(), {'meta': {'draft': True}, 'data': {}})
526
    assert data_class.get(resp.json['data']['id']).status == 'draft'
527

  
528
    resp = get_app(pub).post_json(
529
        url(),
530
        {
531
            'meta': {'backoffice-submission': True},
532
            'data': {},
533
            'context': {'channel': 'mail', 'comments': 'blah'},
534
        },
535
    )
536
    assert data_class.get(resp.json['data']['id']).status == 'wf-new'
537
    assert data_class.get(resp.json['data']['id']).backoffice_submission is True
538
    assert data_class.get(resp.json['data']['id']).user_id is None
539
    assert data_class.get(resp.json['data']['id']).submission_context == {'comments': 'blah'}
540
    assert data_class.get(resp.json['data']['id']).submission_channel == 'mail'
541

  
542
    data_class.wipe()
543

  
544

  
545
def test_formdef_submit_only_one(pub, local_user):
546
    Role.wipe()
547
    role = Role(name='test')
548
    role.store()
549
    local_user.roles = [role.id]
550
    local_user.store()
551

  
552
    FormDef.wipe()
553
    formdef = FormDef()
554
    formdef.name = 'test'
555
    formdef.only_allow_one = True
556
    formdef.fields = [fields.StringField(id='0', label='foobar')]
557
    formdef.store()
558
    data_class = formdef.data_class()
559

  
560
    def url():
561
        signed_url = sign_url(
562
            'http://example.net/api/formdefs/test/submit'
563
            + '?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
564
            '1234',
565
        )
566
        return signed_url[len('http://example.net') :]
567

  
568
    resp = get_app(pub).post_json(url(), {'data': {}})
569
    assert data_class.get(resp.json['data']['id']).user_id == str(local_user.id)
570

  
571
    assert data_class.count() == 1
572

  
573
    resp = get_app(pub).post_json(url(), {'data': {}}, status=403)
574
    assert resp.json['err'] == 1
575
    assert resp.json['err_desc'] == 'only one formdata by user is allowed'
576

  
577
    formdata = data_class.select()[0]
578
    formdata.user_id = '1000'  # change owner
579
    formdata.store()
580

  
581
    resp = get_app(pub).post_json(url(), {'data': {}}, status=200)
582
    assert data_class.get(resp.json['data']['id']).user_id == str(local_user.id)
583
    assert data_class.count() == 2
584

  
585

  
586
def test_formdef_submit_with_varname(pub, local_user):
587
    NamedDataSource.wipe()
588
    data_source = NamedDataSource(name='foobar')
589
    source = [{'id': '1', 'text': 'foo', 'more': 'XXX'}, {'id': '2', 'text': 'bar', 'more': 'YYY'}]
590
    data_source.data_source = {'type': 'formula', 'value': repr(source)}
591
    data_source.store()
592

  
593
    data_source = NamedDataSource(name='foobar_jsonp')
594
    data_source.data_source = {'type': 'formula', 'value': 'http://example.com/jsonp'}
595
    data_source.store()
596

  
597
    Role.wipe()
598
    role = Role(name='test')
599
    role.store()
600
    local_user.roles = [role.id]
601
    local_user.store()
602

  
603
    FormDef.wipe()
604
    formdef = FormDef()
605
    formdef.name = 'test'
606
    formdef.fields = [
607
        fields.StringField(id='0', label='foobar0', varname='foobar0'),
608
        fields.ItemField(id='1', label='foobar1', varname='foobar1', data_source={'type': 'foobar'}),
609
        fields.ItemField(id='2', label='foobar2', varname='foobar2', data_source={'type': 'foobar_jsonp'}),
610
        fields.DateField(id='3', label='foobar3', varname='date'),
611
        fields.FileField(id='4', label='foobar4', varname='file'),
612
        fields.MapField(id='5', label='foobar5', varname='map'),
613
        fields.StringField(id='6', label='foobar6', varname='foobar6'),
614
    ]
615
    formdef.store()
616
    data_class = formdef.data_class()
617

  
618
    resp = get_app(pub).post_json('/api/formdefs/test/submit', {'data': {}}, status=403)
619
    assert resp.json['err'] == 1
620
    assert resp.json['err_desc'] == 'unsigned API call'
621

  
622
    signed_url = sign_url(
623
        'http://example.net/api/formdefs/test/submit'
624
        + '?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
625
        '1234',
626
    )
627
    url = signed_url[len('http://example.net') :]
628
    payload = {
629
        'data': {
630
            'foobar0': 'xxx',
631
            'foobar1': '1',
632
            'foobar1_structured': {
633
                'id': '1',
634
                'text': 'foo',
635
                'more': 'XXX',
636
            },
637
            'foobar2': 'bar',
638
            'foobar2_raw': '10',
639
            'date': '1970-01-01',
640
            'file': {
641
                'filename': 'test.txt',
642
                'content': force_text(base64.b64encode(b'test')),
643
            },
644
            'map': {
645
                'lat': 1.5,
646
                'lon': 2.25,
647
            },
648
        }
649
    }
650
    resp = get_app(pub).post_json(url, payload)
651
    assert resp.json['err'] == 0
652
    assert data_class.get(resp.json['data']['id']).status == 'wf-new'
653
    assert data_class.get(resp.json['data']['id']).user_id == str(local_user.id)
654
    assert data_class.get(resp.json['data']['id']).tracking_code is None
655
    assert data_class.get(resp.json['data']['id']).data['0'] == 'xxx'
656
    assert data_class.get(resp.json['data']['id']).data['1'] == '1'
657
    assert data_class.get(resp.json['data']['id']).data['1_structured'] == source[0]
658
    assert data_class.get(resp.json['data']['id']).data['2'] == '10'
659
    assert data_class.get(resp.json['data']['id']).data['2_display'] == 'bar'
660
    assert data_class.get(resp.json['data']['id']).data['3'] == time.struct_time(
661
        (1970, 1, 1, 0, 0, 0, 3, 1, -1)
662
    )
663

  
664
    assert data_class.get(resp.json['data']['id']).data['4'].orig_filename == 'test.txt'
665
    assert data_class.get(resp.json['data']['id']).data['4'].get_content() == b'test'
666
    assert data_class.get(resp.json['data']['id']).data['5'] == '1.5;2.25'
667
    # test bijectivity
668
    assert (
669
        formdef.fields[3].get_json_value(data_class.get(resp.json['data']['id']).data['3'])
670
        == payload['data']['date']
671
    )
672
    for k in payload['data']['file']:
673
        data = data_class.get(resp.json['data']['id']).data['4']
674
        assert formdef.fields[4].get_json_value(data)[k] == payload['data']['file'][k]
675
    assert (
676
        formdef.fields[5].get_json_value(data_class.get(resp.json['data']['id']).data['5'])
677
        == payload['data']['map']
678
    )
679

  
680
    data_class.wipe()
681

  
682

  
683
def test_formdef_submit_from_wscall(pub, local_user):
684
    test_formdef_submit_with_varname(pub, local_user)
685
    formdef = FormDef.select()[0]
686
    workflow = Workflow.get_default_workflow()
687
    workflow.id = '2'
688
    workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
689
    workflow.backoffice_fields_formdef.fields = [
690
        fields.StringField(id='bo1', label='1st backoffice field', type='string', varname='backoffice_blah'),
691
    ]
692
    workflow.store()
693
    formdef.workflow = workflow
694
    formdef.store()
695

  
696
    formdata = formdef.data_class()()
697
    formdata.just_created()
698

  
699
    upload = PicklableUpload('test.txt', 'text/plain', 'ascii')
700
    upload.receive([b'test'])
701

  
702
    formdata.data = {
703
        '0': 'xxx',
704
        '1': '1',
705
        '1_display': '1',
706
        '1_structured': {
707
            'id': '1',
708
            'text': 'foo',
709
            'more': 'XXX',
710
        },
711
        '2': '10',
712
        '2_display': 'bar',
713
        '3': time.strptime('1970-01-01', '%Y-%m-%d'),
714
        '4': upload,
715
        '5': '1.5;2.25',
716
        'bo1': 'backoffice field',
717
    }
718
    formdata.just_created()
719
    formdata.evolution[-1].status = 'wf-new'
720
    formdata.store()
721

  
722
    payload = json.loads(json.dumps(formdata.get_json_export_dict(), cls=qommon.misc.JSONEncoder))
723
    signed_url = sign_url('http://example.net/api/formdefs/test/submit?orig=coucou', '1234')
724
    url = signed_url[len('http://example.net') :]
725

  
726
    resp = get_app(pub).post_json(url, payload)
727
    assert resp.json['err'] == 0
728
    new_formdata = formdef.data_class().get(resp.json['data']['id'])
729
    assert new_formdata.data['0'] == formdata.data['0']
730
    assert new_formdata.data['1'] == formdata.data['1']
731
    assert new_formdata.data['1_display'] == formdata.data['1_display']
732
    assert new_formdata.data['1_structured'] == formdata.data['1_structured']
733
    assert new_formdata.data['2'] == formdata.data['2']
734
    assert new_formdata.data['2_display'] == formdata.data['2_display']
735
    assert new_formdata.data['3'] == formdata.data['3']
736
    assert new_formdata.data['4'].get_content() == formdata.data['4'].get_content()
737
    assert new_formdata.data['5'] == formdata.data['5']
738
    assert new_formdata.data['bo1'] == formdata.data['bo1']
739
    assert not new_formdata.data.get('6')
740
    assert new_formdata.user_id is None
741

  
742
    # add an extra attribute
743
    payload['extra'] = {'foobar6': 'YYY'}
744
    signed_url = sign_url('http://example.net/api/formdefs/test/submit?orig=coucou', '1234')
745
    url = signed_url[len('http://example.net') :]
746
    resp = get_app(pub).post_json(url, payload)
747
    assert resp.json['err'] == 0
748
    new_formdata = formdef.data_class().get(resp.json['data']['id'])
749
    assert new_formdata.data['0'] == formdata.data['0']
750
    assert new_formdata.data['6'] == 'YYY'
751

  
752
    # add user
753
    formdata.user_id = local_user.id
754
    formdata.store()
755

  
756
    payload = json.loads(json.dumps(formdata.get_json_export_dict(), cls=qommon.misc.JSONEncoder))
757
    signed_url = sign_url('http://example.net/api/formdefs/test/submit?orig=coucou', '1234')
758
    url = signed_url[len('http://example.net') :]
759

  
760
    resp = get_app(pub).post_json(url, payload)
761
    assert resp.json['err'] == 0
762
    new_formdata = formdef.data_class().get(resp.json['data']['id'])
763
    assert str(new_formdata.user_id) == str(local_user.id)
764

  
765
    # test missing map data
766
    del formdata.data['5']
767

  
768
    payload = json.loads(json.dumps(formdata.get_json_export_dict(), cls=qommon.misc.JSONEncoder))
769
    signed_url = sign_url('http://example.net/api/formdefs/test/submit?orig=coucou', '1234')
770
    url = signed_url[len('http://example.net') :]
771

  
772
    resp = get_app(pub).post_json(url, payload)
773
    assert resp.json['err'] == 0
774
    new_formdata = formdef.data_class().get(resp.json['data']['id'])
775
    assert new_formdata.data.get('5') is None
776

  
777

  
778
def test_formdef_submit_structured(pub, local_user):
779
    Role.wipe()
780
    role = Role(name='test')
781
    role.store()
782
    local_user.roles = [role.id]
783
    local_user.store()
784

  
785
    FormDef.wipe()
786
    formdef = FormDef()
787
    formdef.name = 'test'
788
    formdef.fields = [
789
        fields.ItemField(
790
            id='0',
791
            label='foobar',
792
            varname='foobar',
793
            data_source={
794
                'type': 'json',
795
                'value': 'http://datasource.com',
796
            },
797
        ),
798
        fields.ItemField(
799
            id='1',
800
            label='foobar1',
801
            varname='foobar1',
802
            data_source={
803
                'type': 'formula',
804
                'value': '[dict(id=i, text=\'label %s\' % i, foo=i) for i in range(10)]',
805
            },
806
        ),
807
    ]
808
    formdef.store()
809
    data_class = formdef.data_class()
810

  
811
    def url():
812
        signed_url = sign_url(
813
            'http://example.net/api/formdefs/test/submit'
814
            '?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
815
            '1234',
816
        )
817
        return signed_url[len('http://example.net') :]
818

  
819
    with mock.patch('wcs.qommon.misc.urlopen') as urlopen:
820
        urlopen.side_effect = lambda *args: StringIO(
821
            '''\
822
{"data": [{"id": 0, "text": "zéro", "foo": "bar"}, \
823
{"id": 1, "text": "uné", "foo": "bar1"}, \
824
{"id": 2, "text": "deux", "foo": "bar2"}]}'''
825
        )
826
        resp = get_app(pub).post_json(
827
            url(),
828
            {
829
                'data': {
830
                    '0': '0',
831
                    "1": '3',
832
                }
833
            },
834
        )
835

  
836
    formdata = data_class.get(resp.json['data']['id'])
837
    assert formdata.status == 'wf-new'
838
    assert formdata.data['0'] == '0'
839
    assert formdata.data['0_display'] == 'zéro'
840
    assert formdata.data['0_structured'] == {
841
        'id': 0,
842
        'text': 'zéro',
843
        'foo': 'bar',
844
    }
845
    assert formdata.data['1'] == '3'
846
    assert formdata.data['1_display'] == 'label 3'
847
    assert formdata.data['1_structured'] == {
848
        'id': 3,
849
        'text': 'label 3',
850
        'foo': 3,
851
    }
852

  
853
    data_class.wipe()
tests/api/test_user.py
1
# -*- coding: utf-8 -*-
2

  
3
import datetime
4
import os
5

  
6
import pytest
7
from quixote import get_publisher
8
from utilities import clean_temporary_pub, create_temporary_pub, get_app
9

  
10
from wcs import fields
11
from wcs.formdef import FormDef
12
from wcs.qommon.form import PicklableUpload
13
from wcs.qommon.http_request import HTTPRequest
14
from wcs.qommon.ident.password_accounts import PasswordAccount
15
from wcs.roles import Role
16
from wcs.workflows import Workflow, WorkflowVariablesFieldsFormDef
17

  
18
from .utils import sign_uri
19

  
20

  
21
def pytest_generate_tests(metafunc):
22
    if 'pub' in metafunc.fixturenames:
23
        metafunc.parametrize('pub', ['pickle', 'sql'], indirect=True)
24

  
25

  
26
@pytest.fixture
27
def pub(request, emails):
28
    pub = create_temporary_pub(sql_mode=(request.param == 'sql'))
29

  
30
    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
31
    pub.set_app_dir(req)
32
    pub.cfg['identification'] = {'methods': ['password']}
33
    pub.cfg['language'] = {'language': 'en'}
34
    pub.write_cfg()
35

  
36
    open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w').write(
37
        '''\
38
[api-secrets]
39
coucou = 1234
40
'''
41
    )
42

  
43
    return pub
44

  
45

  
46
def teardown_module(module):
47
    clean_temporary_pub()
48

  
49

  
50
@pytest.fixture
51
def local_user():
52
    get_publisher().user_class.wipe()
53
    user = get_publisher().user_class()
54
    user.name = 'Jean Darmette'
55
    user.email = 'jean.darmette@triffouilis.fr'
56
    user.name_identifiers = ['0123456789']
57
    user.store()
58
    return user
59

  
60

  
61
@pytest.fixture
62
def admin_user():
63
    get_publisher().user_class.wipe()
64
    user = get_publisher().user_class()
65
    user.name = 'John Doe Admin'
66
    user.email = 'john.doe@example.com'
67
    user.name_identifiers = ['0123456789']
68
    user.is_admin = True
69
    user.store()
70

  
71
    account = PasswordAccount(id='admin')
72
    account.set_password('admin')
73
    account.user_id = user.id
74
    account.store()
75

  
76
    return user
77

  
78

  
79
def test_roles(pub, local_user):
80
    Role.wipe()
81
    role = Role(name='Hello World')
82
    role.emails = ['toto@example.com', 'zozo@example.com']
83
    role.details = 'kouign amann'
84
    role.store()
85

  
86
    resp = get_app(pub).get('/api/roles', status=403)
87

  
88
    resp = get_app(pub).get(sign_uri('/api/roles'))
89
    assert resp.json['data'][0]['text'] == 'Hello World'
90
    assert resp.json['data'][0]['slug'] == 'hello-world'
91
    assert resp.json['data'][0]['emails'] == ['toto@example.com', 'zozo@example.com']
92
    assert resp.json['data'][0]['emails_to_members'] is False
93
    assert resp.json['data'][0]['details'] == 'kouign amann'
94

  
95
    # also check old endpoint, for compatibility
96
    resp = get_app(pub).get(sign_uri('/roles'), headers={'Accept': 'application/json'})
97
    assert resp.json['data'][0]['text'] == 'Hello World'
98
    assert resp.json['data'][0]['slug'] == 'hello-world'
99
    assert resp.json['data'][0]['emails'] == ['toto@example.com', 'zozo@example.com']
100
    assert resp.json['data'][0]['emails_to_members'] is False
101
    assert resp.json['data'][0]['details'] == 'kouign amann'
102

  
103

  
104
def test_users(pub, local_user):
105
    resp = get_app(pub).get('/api/users/', status=403)
106

  
107
    resp = get_app(pub).get(sign_uri('/api/users/'))
108
    assert resp.json['data'][0]['user_display_name'] == local_user.name
109
    assert resp.json['data'][0]['user_email'] == local_user.email
110
    assert resp.json['data'][0]['user_id'] == local_user.id
111

  
112
    role = Role(name='Foo bar')
113
    role.store()
114
    local_user.roles = [role.id]
115
    local_user.store()
116

  
117
    resp = get_app(pub).get(sign_uri('/api/users/?q=jean'))
118
    assert resp.json['data'][0]['user_email'] == local_user.email
119
    assert len(resp.json['data'][0]['user_roles']) == 1
120
    assert resp.json['data'][0]['user_roles'][0]['name'] == 'Foo bar'
121

  
122
    resp = get_app(pub).get(sign_uri('/api/users/?q=foobar'))
123
    assert len(resp.json['data']) == 0
124

  
125
    from wcs.admin.settings import UserFieldsFormDef
126

  
127
    formdef = UserFieldsFormDef(pub)
128
    formdef.fields.append(fields.StringField(id='3', label='test', type='string'))
129
    formdef.store()
130

  
131
    local_user.form_data = {'3': 'HELLO'}
132
    local_user.set_attributes_from_formdata(local_user.form_data)
133
    local_user.store()
134

  
135
    resp = get_app(pub).get(sign_uri('/api/users/?q=HELLO'))
136
    assert len(resp.json['data']) == 1
137
    resp = get_app(pub).get(sign_uri('/api/users/?q=foobar'))
138
    assert len(resp.json['data']) == 0
139

  
140

  
141
def test_users_unaccent(pub, local_user):
142
    local_user.name = 'Jean Sénisme'
143
    local_user.store()
144
    resp = get_app(pub).get(sign_uri('/api/users/?q=jean'))
145
    assert resp.json['data'][0]['user_email'] == local_user.email
146

  
147
    resp = get_app(pub).get(sign_uri('/api/users/?q=senisme'))
148
    assert resp.json['data'][0]['user_email'] == local_user.email
149

  
150
    resp = get_app(pub).get(sign_uri('/api/users/?q=sénisme'))
151
    assert resp.json['data'][0]['user_email'] == local_user.email
152

  
153
    resp = get_app(pub).get(sign_uri('/api/users/?q=blah'))
154
    assert len(resp.json['data']) == 0
155

  
156

  
157
def test_user_by_nameid(pub, local_user):
158
    resp = get_app(pub).get(sign_uri('/api/users/xyz/', user=local_user), status=404)
159
    local_user.name_identifiers = ['xyz']
160
    local_user.store()
161
    resp = get_app(pub).get(sign_uri('/api/users/xyz/', user=local_user))
162
    assert str(resp.json['id']) == str(local_user.id)
163

  
164

  
165
def test_user_roles(pub, local_user):
166
    local_user.name_identifiers = ['xyz']
167
    local_user.store()
168
    role = Role(name='Foo bar')
169
    role.store()
170
    local_user.roles = [role.id]
171
    local_user.store()
172
    resp = get_app(pub).get(sign_uri('/api/users/xyz/', user=local_user))
173
    assert len(resp.json['user_roles']) == 1
174
    assert resp.json['user_roles'][0]['name'] == 'Foo bar'
175

  
176

  
177
def test_user_forms(pub, local_user):
178
    Workflow.wipe()
179
    workflow = Workflow.get_default_workflow()
180
    workflow.id = '2'
181
    workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow)
182
    workflow.variables_formdef.fields.append(
183
        fields.DateField(label='Test', type='date', varname='option_date')
184
    )
185
    workflow.store()
186

  
187
    FormDef.wipe()
188
    formdef = FormDef()
189
    formdef.name = 'test'
190
    formdef.fields = [
191
        fields.StringField(id='0', label='foobar', varname='foobar'),
192
        fields.StringField(id='1', label='foobar2'),
193
        fields.DateField(id='2', label='date', type='date', varname='date'),
194
    ]
195
    formdef.keywords = 'hello, world'
196
    formdef.disabled = False
197
    formdef.enable_tracking_codes = True
198
    formdef.workflow = workflow
199
    formdef.workflow_options = {'option_date': datetime.date(2020, 1, 15).timetuple()}
200
    formdef.store()
201
    formdef.data_class().wipe()
202

  
203
    resp = get_app(pub).get(sign_uri('/api/user/forms', user=local_user))
204
    assert resp.json['err'] == 0
205
    assert len(resp.json['data']) == 0
206

  
207
    formdata = formdef.data_class()()
208
    formdata.data = {
209
        '0': 'foo@localhost',
210
        '1': 'xxx',
211
        '2': datetime.date(2020, 1, 15).timetuple(),
212
    }
213
    formdata.user_id = local_user.id
214
    formdata.just_created()
215
    formdata.jump_status('new')
216
    formdata.store()
217

  
218
    resp = get_app(pub).get(sign_uri('/api/user/forms', user=local_user))
219
    resp2 = get_app(pub).get(sign_uri('/myspace/forms', user=local_user))
220
    resp3 = get_app(pub).get(sign_uri('/api/users/%s/forms' % local_user.id))
221
    assert resp.json['err'] == 0
222
    assert len(resp.json['data']) == 1
223
    assert resp.json['data'][0]['form_name'] == 'test'
224
    assert resp.json['data'][0]['form_slug'] == 'test'
225
    assert resp.json['data'][0]['form_status'] == 'New'
226
    assert datetime.datetime.strptime(resp.json['data'][0]['form_receipt_datetime'], '%Y-%m-%dT%H:%M:%S')
227
    assert resp.json['data'][0]['keywords'] == ['hello', 'world']
228
    assert resp.json == resp2.json == resp3.json
229

  
230
    resp = get_app(pub).get(sign_uri('/api/user/forms?full=on', user=local_user))
231
    assert resp.json['err'] == 0
232
    assert resp.json['data'][0]['fields']['foobar'] == 'foo@localhost'
233
    assert resp.json['data'][0]['fields']['date'] == '2020-01-15'
234
    assert resp.json['data'][0]['keywords'] == ['hello', 'world']
235
    assert resp.json['data'][0]['form_option_option_date'] == '2020-01-15'
236
    resp2 = get_app(pub).get(sign_uri('/api/user/forms?&full=on', user=local_user))
237
    assert resp.json == resp2.json
238

  
239
    formdef.disabled = True
240
    formdef.store()
241
    resp = get_app(pub).get(sign_uri('/api/user/forms', user=local_user))
242
    assert resp.json['err'] == 0
243
    assert len(resp.json['data']) == 1
244

  
245
    # check digest is part of contents
246
    formdef.digest_template = 'XYZ'
247
    formdef.data_class().get(formdata.id).store()
248
    assert formdef.data_class().get(formdata.id).digest == 'XYZ'
249
    resp = get_app(pub).get(sign_uri('/api/user/forms', user=local_user))
250
    assert resp.json['data'][0]['form_digest'] == 'XYZ'
251

  
252
    resp = get_app(pub).get(sign_uri('/api/user/forms?NameID=xxx'))
253
    assert resp.json == {'err': 1, 'err_desc': 'unknown NameID', 'data': []}
254
    resp2 = get_app(pub).get(sign_uri('/api/user/forms?&NameID=xxx'))
255
    assert resp.json == resp2.json
256

  
257
    formdata = formdef.data_class()()
258
    formdata.user_id = local_user.id
259
    formdata.status = 'draft'
260
    formdata.receipt_time = datetime.datetime(2015, 1, 1).timetuple()
261
    formdata.store()
262

  
263
    resp = get_app(pub).get(sign_uri('/api/user/forms', user=local_user))
264
    assert resp.json['err'] == 0
265
    assert len(resp.json['data']) == 1
266

  
267
    resp = get_app(pub).get(sign_uri('/api/user/forms?include-drafts=true', user=local_user))
268
    assert resp.json['err'] == 0
269
    assert len(resp.json['data']) == 1
270

  
271
    formdef.disabled = False
272
    formdef.store()
273

  
274
    resp = get_app(pub).get(sign_uri('/api/user/forms?include-drafts=true', user=local_user))
275
    assert resp.json['err'] == 0
276
    assert len(resp.json['data']) == 2
277

  
278
    formdata = formdef.data_class()()
279
    formdata.data = {'0': 'foo@localhost', '1': 'xyy'}
280
    formdata.user_id = local_user.id
281
    formdata.just_created()
282
    formdata.receipt_time = (datetime.datetime.now() + datetime.timedelta(days=1)).timetuple()
283
    formdata.jump_status('new')
284
    formdata.store()
285

  
286
    resp = get_app(pub).get(sign_uri('/api/user/forms', user=local_user))
287
    assert len(resp.json['data']) == 2
288
    resp2 = get_app(pub).get(sign_uri('/api/user/forms?sort=desc', user=local_user))
289
    assert len(resp2.json['data']) == 2
290
    assert resp2.json['data'][0] == resp.json['data'][1]
291
    assert resp2.json['data'][1] == resp.json['data'][0]
292

  
293

  
294
def test_user_forms_limit_offset(pub, local_user):
295
    if not pub.is_using_postgresql():
296
        pytest.skip('this requires SQL')
297
        return
298

  
299
    FormDef.wipe()
300
    formdef = FormDef()
301
    formdef.name = 'test limit offset'
302
    formdef.fields = [
303
        fields.StringField(id='0', label='foobar', varname='foobar'),
304
        fields.StringField(id='1', label='foobar2'),
305
    ]
306
    formdef.keywords = 'hello, world'
307
    formdef.disabled = False
308
    formdef.enable_tracking_codes = False
309
    formdef.store()
310
    formdef.data_class().wipe()
311

  
312
    for i in range(50):
313
        formdata = formdef.data_class()()
314
        formdata.data = {'0': 'foo@localhost', '1': str(i)}
315
        formdata.user_id = local_user.id
316
        formdata.just_created()
317
        formdata.receipt_time = (datetime.datetime.now() + datetime.timedelta(days=i)).timetuple()
318
        formdata.jump_status('new')
319
        formdata.store()
320

  
321
    resp = get_app(pub).get(sign_uri('/api/users/%s/forms' % local_user.id))
322
    assert resp.json['err'] == 0
323
    assert len(resp.json['data']) == 50
324

  
325
    resp = get_app(pub).get(sign_uri('/api/users/%s/forms?limit=10' % local_user.id))
326
    assert resp.json['err'] == 0
327
    assert len(resp.json['data']) == 10
328
    assert [x['form_number_raw'] for x in resp.json['data']] == [str(x) for x in range(1, 11)]
329

  
330
    resp = get_app(pub).get(sign_uri('/api/users/%s/forms?limit=10&offset=45' % local_user.id))
331
    assert resp.json['err'] == 0
332
    assert len(resp.json['data']) == 5
333
    assert [x['form_number_raw'] for x in resp.json['data']] == [str(x) for x in range(46, 51)]
334

  
335
    resp = get_app(pub).get(sign_uri('/api/users/%s/forms?limit=10&sort=desc' % local_user.id))
336
    assert resp.json['err'] == 0
337
    assert len(resp.json['data']) == 10
338
    assert [x['form_number_raw'] for x in resp.json['data']] == [str(x) for x in range(50, 40, -1)]
339

  
340

  
341
def test_user_forms_from_agent(pub, local_user):
342
    Role.wipe()
343
    role = Role(name='Foo bar')
344
    role.store()
345

  
346
    agent_user = get_publisher().user_class()
347
    agent_user.name = 'Agent'
348
    agent_user.email = 'agent@example.com'
349
    agent_user.name_identifiers = ['ABCDE']
350
    agent_user.roles = [role.id]
351
    agent_user.store()
352

  
353
    FormDef.wipe()
354
    formdef = FormDef()
355
    formdef.name = 'test'
356
    formdef.fields = [
357
        fields.StringField(id='0', label='foobar', varname='foobar'),
358
        fields.StringField(id='1', label='foobar2'),
359
    ]
360
    formdef.store()
361
    formdef.data_class().wipe()
362

  
363
    formdata = formdef.data_class()()
364
    formdata.data = {'0': 'foo@localhost', '1': 'xxx'}
365
    formdata.user_id = local_user.id
366
    formdata.just_created()
367
    formdata.jump_status('new')
368
    formdata.store()
369

  
370
    resp = get_app(pub).get(sign_uri('/api/users/%s/forms' % local_user.id, user=agent_user))
371
    assert resp.json['err'] == 0
372
    assert len(resp.json['data']) == 1
373
    assert resp.json['data'][0]['form_name'] == 'test'
374
    assert resp.json['data'][0]['form_slug'] == 'test'
375
    assert resp.json['data'][0]['form_status'] == 'New'
376
    assert resp.json['data'][0]['readable'] is False
377

  
378
    formdef.skip_from_360_view = True
379
    formdef.store()
380

  
381
    resp = get_app(pub).get(sign_uri('/api/users/%s/forms' % local_user.id, user=agent_user))
382
    assert len(resp.json['data']) == 0
383

  
384
    formdef.workflow_roles = {'_receiver': str(role.id)}
385
    formdef.store()
386
    formdef.data_class().rebuild_security()
387
    resp = get_app(pub).get(sign_uri('/api/users/%s/forms' % local_user.id, user=agent_user))
388
    assert len(resp.json['data']) == 1
389

  
390
    agent_user.roles = []
391
    agent_user.store()
392
    get_app(pub).get(sign_uri('/api/users/%s/forms' % local_user.id, user=agent_user), status=403)
393

  
394

  
395
def test_user_drafts(pub, local_user):
396
    FormDef.wipe()
397
    formdef = FormDef()
398
    formdef.name = 'test'
399
    formdef.fields = [
400
        fields.StringField(id='0', label='foobar', varname='foobar'),
401
        fields.StringField(id='1', label='foobar2'),
402
        fields.FileField(id='2', label='foobar3', varname='file'),
403
    ]
404
    formdef.keywords = 'hello, world'
405
    formdef.disabled = False
406
    formdef.enable_tracking_codes = True
407
    formdef.store()
408

  
409
    formdef.data_class().wipe()
410

  
411
    resp = get_app(pub).get(sign_uri('/api/user/drafts', user=local_user))
412
    assert resp.json['err'] == 0
413
    assert len(resp.json['data']) == 0
414

  
415
    formdata = formdef.data_class()()
416
    upload = PicklableUpload('test.txt', 'text/plain', 'ascii')
417
    upload.receive([b'base64me'])
418
    formdata.data = {'0': 'foo@localhost', '1': 'xxx', '2': upload}
419
    formdata.user_id = local_user.id
420
    formdata.page_no = 1
421
    formdata.status = 'draft'
422
    formdata.receipt_time = datetime.datetime(2015, 1, 1).timetuple()
423
    formdata.store()
424

  
425
    resp = get_app(pub).get(sign_uri('/api/user/drafts', user=local_user))
426
    resp2 = get_app(pub).get(sign_uri('/myspace/drafts', user=local_user))
427
    assert resp.json['err'] == 0
428
    assert len(resp.json['data']) == 1
429
    assert resp.json == resp2.json
430
    assert 'fields' not in resp.json['data'][0]
431
    assert resp.json['data'][0]['keywords'] == ['hello', 'world']
432

  
433
    resp = get_app(pub).get(sign_uri('/api/user/drafts?full=on', user=local_user))
434
    assert resp.json['err'] == 0
435
    assert 'fields' in resp.json['data'][0]
436
    assert resp.json['data'][0]['fields']['foobar'] == 'foo@localhost'
437
    assert 'url' in resp.json['data'][0]['fields']['file']
438
    assert 'content' not in resp.json['data'][0]['fields']['file']  # no file content in full lists
439
    assert resp.json['data'][0]['keywords'] == ['hello', 'world']
440

  
441
    formdef.enable_tracking_codes = False
442
    formdef.store()
443
    resp = get_app(pub).get(sign_uri('/api/user/drafts', user=local_user))
444
    assert resp.json['err'] == 0
445
    assert len(resp.json['data']) == 1
446

  
447
    formdef.enable_tracking_codes = True
448
    formdef.disabled = True
449
    formdef.store()
450
    resp = get_app(pub).get(sign_uri('/api/user/drafts', user=local_user))
451
    assert resp.json['err'] == 0
452
    assert len(resp.json['data']) == 0
453

  
454
    resp = get_app(pub).get(sign_uri('/api/user/drafts?NameID=xxx'))
455
    assert resp.json == {'err': 1, 'err_desc': 'unknown NameID', 'data': []}
456
    resp2 = get_app(pub).get(sign_uri('/api/user/drafts?&NameID=xxx'))
457
    assert resp.json == resp2.json
tests/api/test_utils.py
1 1
import pytest
2 2
from quixote import cleanup, get_response
3
from utilities import clean_temporary_pub, create_temporary_pub
3 4

  
4 5
from wcs import sessions
5 6
from wcs.api_utils import get_query_flag
6 7
from wcs.qommon.http_request import HTTPRequest
7 8

  
8
from utilities import create_temporary_pub, clean_temporary_pub
9

  
10 9

  
11 10
def setup_module(module):
12 11
    cleanup()
tests/api/test_workflow.py
1
# -*- coding: utf-8 -*-
2

  
3
import os
4

  
5
import pytest
6
from quixote import get_publisher
7
from utilities import clean_temporary_pub, create_temporary_pub, get_app
8

  
9
from wcs import fields
10
from wcs.formdef import FormDef
11
from wcs.qommon.http_request import HTTPRequest
12
from wcs.qommon.ident.password_accounts import PasswordAccount
13
from wcs.roles import Role
14
from wcs.wf.jump import JumpWorkflowStatusItem
15
from wcs.wf.register_comment import RegisterCommenterWorkflowStatusItem
16
from wcs.workflows import Workflow
17

  
18
from .utils import sign_uri
19

  
20

  
21
def pytest_generate_tests(metafunc):
22
    if 'pub' in metafunc.fixturenames:
23
        metafunc.parametrize('pub', ['pickle', 'sql'], indirect=True)
24

  
25

  
26
@pytest.fixture
27
def pub(request, emails):
28
    pub = create_temporary_pub(sql_mode=(request.param == 'sql'))
29

  
30
    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
31
    pub.set_app_dir(req)
32
    pub.cfg['identification'] = {'methods': ['password']}
33
    pub.cfg['language'] = {'language': 'en'}
34
    pub.write_cfg()
35

  
36
    open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w').write(
37
        '''\
38
[api-secrets]
39
coucou = 1234
40
'''
41
    )
42

  
43
    return pub
44

  
45

  
46
def teardown_module(module):
47
    clean_temporary_pub()
48

  
49

  
50
@pytest.fixture
51
def local_user():
52
    get_publisher().user_class.wipe()
53
    user = get_publisher().user_class()
54
    user.name = 'Jean Darmette'
55
    user.email = 'jean.darmette@triffouilis.fr'
56
    user.name_identifiers = ['0123456789']
57
    user.store()
58
    return user
59

  
60

  
61
@pytest.fixture
62
def admin_user():
63
    get_publisher().user_class.wipe()
64
    user = get_publisher().user_class()
65
    user.name = 'John Doe Admin'
66
    user.email = 'john.doe@example.com'
67
    user.name_identifiers = ['0123456789']
68
    user.is_admin = True
69
    user.store()
70

  
71
    account = PasswordAccount(id='admin')
72
    account.set_password('admin')
73
    account.user_id = user.id
74
    account.store()
75

  
76
    return user
77

  
78

  
79
def test_workflow_trigger(pub, local_user):
80
    workflow = Workflow(name='test')
81
    st1 = workflow.add_status('Status1', 'st1')
82
    jump = JumpWorkflowStatusItem()
83
    jump.trigger = 'XXX'
84
    jump.status = 'st2'
85
    st1.items.append(jump)
86
    jump.parent = st1
87
    workflow.add_status('Status2', 'st2')
88
    workflow.store()
89

  
90
    FormDef.wipe()
91
    formdef = FormDef()
92
    formdef.name = 'test'
93
    formdef.fields = []
94
    formdef.workflow_id = workflow.id
95
    formdef.store()
96

  
97
    formdef.data_class().wipe()
98
    formdata = formdef.data_class()()
99
    formdata.just_created()
100
    formdata.store()
101
    assert formdef.data_class().get(formdata.id).status == 'wf-st1'
102

  
103
    get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=200)
104
    assert formdef.data_class().get(formdata.id).status == 'wf-st2'
105

  
106
    # check with trailing slash
107
    formdata.store()  # reset
108
    get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX/'), status=200)
109
    assert formdef.data_class().get(formdata.id).status == 'wf-st2'
110

  
111
    Role.wipe()
112
    role = Role(name='xxx')
113
    role.store()
114

  
115
    jump.by = [role.id]
116
    workflow.store()
117

  
118
    formdata.store()  # (will get back to wf-st1)
119
    get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=403)
120

  
121
    get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX', user=local_user), status=403)
122

  
123
    local_user.roles = [role.id]
124
    local_user.store()
125
    get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX', user=local_user), status=200)
126

  
127

  
128
def test_workflow_trigger_with_data(pub, local_user):
129
    workflow = Workflow(name='test')
130
    st1 = workflow.add_status('Status1', 'st1')
131
    jump = JumpWorkflowStatusItem()
132
    jump.trigger = 'XXX'
133
    jump.status = 'st2'
134
    st1.items.append(jump)
135
    jump.parent = st1
136
    workflow.add_status('Status2', 'st2')
137
    workflow.store()
138

  
139
    FormDef.wipe()
140
    formdef = FormDef()
141
    formdef.name = 'test'
142
    formdef.fields = []
143
    formdef.workflow_id = workflow.id
144
    formdef.store()
145

  
146
    formdef.data_class().wipe()
147
    formdata = formdef.data_class()()
148
    formdata.just_created()
149
    formdata.store()
150

  
151
    get_app(pub).post_json(
152
        sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=200, params={'test': 'data'}
153
    )
154
    assert formdef.data_class().get(formdata.id).status == 'wf-st2'
155
    assert formdef.data_class().get(formdata.id).workflow_data == {'test': 'data'}
156

  
157
    # post with empty dictionary
158
    formdata.store()  # reset
159
    get_app(pub).post_json(sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=200, params={})
160
    assert formdef.data_class().get(formdata.id).status == 'wf-st2'
161
    assert not formdef.data_class().get(formdata.id).workflow_data
162

  
163
    # post with empty data
164
    formdata.store()  # reset
165
    get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=200)
166
    assert formdef.data_class().get(formdata.id).status == 'wf-st2'
167
    assert not formdef.data_class().get(formdata.id).workflow_data
168

  
169
    # post with empty data, but declare json content-type
170
    formdata.store()  # reset
171
    get_app(pub).post(
172
        sign_uri(formdata.get_url() + 'jump/trigger/XXX'),
173
        status=200,
174
        headers={'content-type': 'application/json'},
175
    )
176
    assert formdef.data_class().get(formdata.id).status == 'wf-st2'
177
    assert not formdef.data_class().get(formdata.id).workflow_data
178

  
179
    # post with invalid JSON data
180
    formdata.store()  # reset
181
    get_app(pub).post(
182
        sign_uri(formdata.get_url() + 'jump/trigger/XXX'),
183
        status=400,
184
        headers={'content-type': 'application/json'},
185
        params='ERROR',
186
    )
187

  
188

  
189
def test_workflow_trigger_with_condition(pub, local_user):
190
    workflow = Workflow(name='test')
191
    st1 = workflow.add_status('Status1', 'st1')
192
    jump = JumpWorkflowStatusItem()
193
    jump.trigger = 'XXX'
194
    jump.condition = {'type': 'django', 'value': 'form_var_foo == "bar"'}
195
    jump.status = 'st2'
196
    st1.items.append(jump)
197
    jump.parent = st1
198
    workflow.add_status('Status2', 'st2')
199
    workflow.store()
200

  
201
    FormDef.wipe()
202
    formdef = FormDef()
203
    formdef.name = 'test'
204
    formdef.fields = [fields.StringField(id='0', label='foo', varname='foo')]
205
    formdef.workflow_id = workflow.id
206
    formdef.store()
207

  
208
    formdef.data_class().wipe()
209
    formdata = formdef.data_class()()
210
    formdata.data = {'0': 'foo'}
211
    formdata.just_created()
212
    formdata.store()
213
    assert formdef.data_class().get(formdata.id).status == 'wf-st1'
214

  
215
    resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'), status=403)
216
    assert resp.json == {'err_desc': 'unmet condition', 'err': 1}
217
    assert formdef.data_class().get(formdata.id).status == 'wf-st1'
218
    # check without json
219
    resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX', format=None), status=403)
220
    assert resp.content_type == 'text/html'
221

  
222
    formdata.data['0'] = 'bar'
223
    formdata.store()
224
    resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'))
225
    assert resp.json == {'err': 0, 'url': None}
226

  
227

  
228
def test_workflow_trigger_jump_once(pub, local_user):
229
    workflow = Workflow(name='test')
230
    st1 = workflow.add_status('Status1', 'st1')
231
    st2 = workflow.add_status('Status2', 'st2')
232
    workflow.add_status('Status3', 'st3')
233
    jump = JumpWorkflowStatusItem()
234
    jump.trigger = 'XXX'
235
    jump.status = 'st2'
236
    st1.items.append(jump)
237
    jump.parent = st1
238
    jump = JumpWorkflowStatusItem()
239
    jump.trigger = 'XXX'
240
    jump.status = 'st3'
241
    st2.items.append(jump)
242
    jump.parent = st2
243
    workflow.store()
244

  
245
    FormDef.wipe()
246
    formdef = FormDef()
247
    formdef.name = 'test'
248
    formdef.fields = []
249
    formdef.workflow_id = workflow.id
250
    formdef.store()
251

  
252
    formdef.data_class().wipe()
253
    formdata = formdef.data_class()()
254
    formdata.just_created()
255
    formdata.store()
256
    assert formdef.data_class().get(formdata.id).status == 'wf-st1'
257

  
258
    resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'))
259
    assert resp.json == {'err': 0, 'url': None}
260
    assert formdef.data_class().get(formdata.id).status == 'wf-st2'
261

  
262
    resp = get_app(pub).post(sign_uri(formdata.get_url() + 'jump/trigger/XXX'))
263
    assert resp.json == {'err': 0, 'url': None}
264
    assert formdef.data_class().get(formdata.id).status == 'wf-st3'
265

  
266

  
267
def test_workflow_global_webservice_trigger(pub, local_user):
268
    workflow = Workflow(name='test')
269
    workflow.add_status('Status1', 'st1')
270

  
271
    ac1 = workflow.add_global_action('Action', 'ac1')
272
    trigger = ac1.append_trigger('webservice')
273
    trigger.identifier = 'plop'
274

  
275
    add_to_journal = RegisterCommenterWorkflowStatusItem()
276
    add_to_journal.id = '_add_to_journal'
277
    add_to_journal.comment = 'HELLO WORLD'
278
    ac1.items.append(add_to_journal)
279
    add_to_journal.parent = ac1
280

  
281
    workflow.store()
282

  
283
    FormDef.wipe()
284
    formdef = FormDef()
285
    formdef.name = 'test'
286
    formdef.fields = []
287
    formdef.workflow_id = workflow.id
288
    formdef.store()
289

  
290
    formdef.data_class().wipe()
291
    formdata = formdef.data_class()()
292
    formdata.just_created()
293
    formdata.store()
294
    assert formdef.data_class().get(formdata.id).status == 'wf-st1'
295

  
296
    # call to undefined hook
297
    get_app(pub).post(sign_uri(formdata.get_url() + 'hooks/XXX/'), status=404)
298
    get_app(pub).post(sign_uri(formdata.get_api_url() + 'hooks/XXX/'), status=404)
299

  
300
    # anonymous call
301
    get_app(pub).post(formdata.get_url() + 'hooks/plop/', status=200)
302
    assert formdef.data_class().get(formdata.id).evolution[-1].parts[-1].content == 'HELLO WORLD'
303

  
304
    add_to_journal.comment = 'HELLO WORLD 2'
305
    workflow.store()
306
    get_app(pub).post(formdata.get_api_url() + 'hooks/plop/', status=200)
307
    assert formdef.data_class().get(formdata.id).evolution[-1].parts[-1].content == 'HELLO WORLD 2'
308

  
309
    # call requiring user
310
    add_to_journal.comment = 'HELLO WORLD 3'
311
    trigger.roles = ['logged-users']
312
    workflow.store()
313
    get_app(pub).post(formdata.get_api_url() + 'hooks/plop/', status=403)
314
    get_app(pub).post(sign_uri(formdata.get_api_url() + 'hooks/plop/'), status=200)
315
    assert formdef.data_class().get(formdata.id).evolution[-1].parts[-1].content == 'HELLO WORLD 3'
316

  
317
    # call requiring roles
318
    add_to_journal.comment = 'HELLO WORLD 4'
319
    trigger.roles = ['logged-users']
320
    workflow.store()
321
    Role.wipe()
322
    role = Role(name='xxx')
323
    role.store()
324
    trigger.roles = [role.id]
325
    workflow.store()
326
    get_app(pub).post(sign_uri(formdata.get_api_url() + 'hooks/plop/'), status=403)
327
    get_app(pub).post(sign_uri(formdata.get_api_url() + 'hooks/plop/', user=local_user), status=403)
328

  
329
    local_user.roles = [role.id]
330
    local_user.store()
331
    get_app(pub).post(sign_uri(formdata.get_api_url() + 'hooks/plop/', user=local_user), status=200)
332
    assert formdef.data_class().get(formdata.id).evolution[-1].parts[-1].content == 'HELLO WORLD 4'
333

  
334
    # call adding data
335
    add_to_journal.comment = 'HELLO {{plop_test}}'
336
    workflow.store()
337
    get_app(pub).post_json(
338
        sign_uri(formdata.get_api_url() + 'hooks/plop/', user=local_user), {'test': 'foobar'}, status=200
339
    )
340
    # (django templating make it turn into HTML)
341
    assert formdef.data_class().get(formdata.id).evolution[-1].parts[-1].content == '<div>HELLO foobar</div>'
342

  
343
    # call adding data but with no actions
344
    ac1.items = []
345
    workflow.store()
346
    get_app(pub).post_json(
347
        sign_uri(formdata.get_api_url() + 'hooks/plop/', user=local_user), {'test': 'BAR'}, status=200
348
    )
349
    assert formdef.data_class().get(formdata.id).workflow_data == {'plop': {'test': 'BAR'}}
350

  
351

  
352
def test_workflow_global_webservice_trigger_no_trailing_slash(pub, local_user):
353
    workflow = Workflow(name='test')
354
    workflow.add_status('Status1', 'st1')
355

  
356
    ac1 = workflow.add_global_action('Action', 'ac1')
357
    trigger = ac1.append_trigger('webservice')
358
    trigger.identifier = 'plop'
359

  
360
    add_to_journal = RegisterCommenterWorkflowStatusItem()
361
    add_to_journal.id = '_add_to_journal'
362
    add_to_journal.comment = 'HELLO WORLD'
363
    ac1.items.append(add_to_journal)
364
    add_to_journal.parent = ac1
365

  
366
    workflow.store()
367

  
368
    FormDef.wipe()
369
    formdef = FormDef()
370
    formdef.name = 'test'
371
    formdef.fields = []
372
    formdef.workflow_id = workflow.id
373
    formdef.store()
374

  
375
    formdef.data_class().wipe()
376
    formdata = formdef.data_class()()
377
    formdata.just_created()
378
    formdata.store()
379
    assert formdef.data_class().get(formdata.id).status == 'wf-st1'
380

  
381
    # call to undefined hook
382
    get_app(pub).post(sign_uri(formdata.get_url() + 'hooks/XXX'), status=404)
383
    get_app(pub).post(sign_uri(formdata.get_api_url() + 'hooks/XXX'), status=404)
384

  
385
    # anonymous call
386
    get_app(pub).post(formdata.get_url() + 'hooks/plop', status=200)
387
    assert formdef.data_class().get(formdata.id).evolution[-1].parts[-1].content == 'HELLO WORLD'
tests/api/utils.py
1
# -*- coding: utf-8 -*-
2

  
3
import base64
4
import datetime
5
import hashlib
6
import hmac
7

  
8
from django.utils.encoding import force_bytes
9
from django.utils.six.moves.urllib import parse as urllib
10
from django.utils.six.moves.urllib import parse as urlparse
11

  
12

  
13
def sign_uri(uri, user=None, format='json'):
14
    timestamp = datetime.datetime.utcnow().isoformat()[:19] + 'Z'
15
    scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri)
16
    if query:
17
        query += '&'
18
    if format:
19
        query += 'format=%s&' % format
20
    query += 'orig=coucou&algo=sha256&timestamp=' + timestamp
21
    if user:
22
        query += '&email=' + urllib.quote(user.email)
23
    query += '&signature=%s' % urllib.quote(
24
        base64.b64encode(hmac.new(b'1234', force_bytes(query), hashlib.sha256).digest())
25
    )
26
    return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
0
-