wcs/tests/api/test_carddef.py

557 lines
20 KiB
Python

# -*- coding: utf-8 -*-
import base64
import json
import os
import time
import mock
import pytest
from django.utils.encoding import force_text
from django.utils.six import StringIO
from django.utils.six.moves.urllib import parse as urllib
from quixote import get_publisher
from utilities import clean_temporary_pub, create_temporary_pub, get_app
from wcs import fields, qommon
from wcs.api_utils import sign_url
from wcs.carddef import CardDef
from wcs.categories import CardDefCategory
from wcs.data_sources import NamedDataSource
from wcs.qommon.form import PicklableUpload
from wcs.qommon.http_request import HTTPRequest
from wcs.roles import Role
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef
from .utils import sign_uri
def pytest_generate_tests(metafunc):
if 'pub' in metafunc.fixturenames:
metafunc.parametrize('pub', ['pickle', 'sql'], indirect=True)
@pytest.fixture
def pub(request, emails):
pub = create_temporary_pub(sql_mode=(request.param == 'sql'))
req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
pub.set_app_dir(req)
pub.cfg['identification'] = {'methods': ['password']}
pub.cfg['language'] = {'language': 'en'}
pub.write_cfg()
open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w').write(
'''\
[api-secrets]
coucou = 1234
'''
)
return pub
def teardown_module(module):
clean_temporary_pub()
@pytest.fixture
def local_user():
get_publisher().user_class.wipe()
user = get_publisher().user_class()
user.name = 'Jean Darmette'
user.email = 'jean.darmette@triffouilis.fr'
user.name_identifiers = ['0123456789']
user.store()
return user
def test_cards(pub, local_user):
Role.wipe()
role = Role(name='test')
role.store()
local_user.roles = [role.id]
local_user.store()
CardDefCategory.wipe()
category = CardDefCategory()
category.name = 'Category A'
category.store()
CardDef.wipe()
carddef = CardDef()
carddef.name = 'test'
carddef.fields = [fields.StringField(id='0', label='foobar', varname='foo')]
carddef.workflow_roles = {'_viewer': role.id}
carddef.digest_template = 'bla {{ form_var_foo }} xxx'
carddef.store()
carddef.data_class().wipe()
formdata = carddef.data_class()()
formdata.data = {'0': 'blah'}
formdata.just_created()
formdata.store()
custom_view = pub.custom_view_class()
custom_view.title = 'shared carddef custom view'
custom_view.formdef = carddef
custom_view.columns = {'list': [{'id': '0'}]}
custom_view.filters = {}
custom_view.visibility = 'any'
custom_view.store()
custom_view = pub.custom_view_class()
custom_view.title = 'private carddef custom view'
custom_view.formdef = carddef
custom_view.columns = {'list': [{'id': '0'}]}
custom_view.filters = {}
custom_view.visibility = 'owner'
custom_view.user = local_user
custom_view.store()
custom_view = pub.custom_view_class()
custom_view.title = 'datasource carddef custom view'
custom_view.formdef = carddef
custom_view.columns = {'list': [{'id': '0'}]}
custom_view.filters = {}
custom_view.visibility = 'datasource'
custom_view.store()
resp = get_app(pub).get('/api/cards/@list', status=403)
resp = get_app(pub).get(sign_uri('/api/cards/@list'))
assert len(resp.json['data']) == 1
assert resp.json['data'][0]['slug'] == 'test'
assert resp.json['data'][0]['category_slug'] is None
assert resp.json['data'][0]['category_name'] is None
assert resp.json['data'][0]['custom_views'] == [
{'id': 'datasource-carddef-custom-view', 'text': 'datasource carddef custom view'},
{'id': 'shared-carddef-custom-view', 'text': 'shared carddef custom view'},
]
carddef.category = category
carddef.store()
resp = get_app(pub).get(sign_uri('/api/cards/@list'))
assert len(resp.json['data']) == 1
assert resp.json['data'][0]['slug'] == 'test'
assert resp.json['data'][0]['category_slug'] == 'category-a'
assert resp.json['data'][0]['category_name'] == 'Category A'
# signed but anonymous
resp = get_app(pub).get(sign_uri('/api/cards/test/list?NameID='), status=403)
# signed without specifying any user -> get everything
resp = get_app(pub).get(sign_uri('/api/cards/test/list'))
assert len(resp.json['data']) == 1
resp = get_app(pub).get(sign_uri('/api/cards/test/list?NameID=%s' % local_user.name_identifiers[0]))
assert len(resp.json['data']) == 1
assert resp.json['data'][0]['display_id'] == formdata.get_display_id()
assert resp.json['data'][0]['display_name'] == formdata.get_display_name()
assert resp.json['data'][0]['digest'] == formdata.digest
assert resp.json['data'][0]['text'] == formdata.digest
resp = get_app(pub).get(
sign_uri('/api/cards/test/list?NameID=%s&full=on' % local_user.name_identifiers[0])
)
assert resp.json['data'][0]['fields']['foo'] == 'blah'
assert resp.json['data'][0]['digest'] == formdata.digest
assert resp.json['data'][0]['text'] == formdata.digest
# get schema
resp = get_app(pub).get(sign_uri('/api/cards/test/@schema'), status=200)
assert len(resp.json['fields']) == 1
assert resp.json['fields'][0]['label'] == 'foobar'
assert resp.json['fields'][0]['varname'] == 'foo'
def test_cards_import_csv(pub, local_user):
Role.wipe()
role = Role(name='test')
role.store()
local_user.roles = [role.id]
local_user.store()
CardDef.wipe()
carddef = CardDef()
carddef.name = 'test'
carddef.fields = [
fields.StringField(id='0', label='foobar', varname='foo'),
fields.StringField(id='1', label='foobar2', varname='foo2'),
]
carddef.workflow_roles = {'_viewer': role.id}
carddef.backoffice_submission_roles = [role.id]
carddef.digest_template = 'bla {{ form_var_foo }} xxx'
carddef.store()
carddef.data_class().wipe()
get_app(pub).get(sign_uri('/api/cards/test/import-csv'), status=405)
get_app(pub).put(sign_uri('/api/cards/test/import-csv'), status=403)
get_app(pub).put(
sign_uri('/api/cards/test/import-csv', user=local_user),
params=b'foobar;foobar2\nfirst entry;plop\nsecond entry;plop\n',
headers={'content-type': 'text/csv'},
)
assert carddef.data_class().count() == 2
assert set([x.data['0'] for x in carddef.data_class().select()]) == {'first entry', 'second entry'}
def test_post_invalid_json(pub, local_user):
resp = get_app(pub).post(
'/api/cards/test/submit', params='not a json payload', content_type='application/json', status=400
)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'Invalid request'
def test_card_submit(pub, local_user):
Role.wipe()
role = Role(name='test')
role.store()
local_user.roles = [role.id]
local_user.store()
CardDef.wipe()
carddef = CardDef()
carddef.name = 'test'
carddef.fields = [fields.StringField(id='0', label='foobar')]
carddef.store()
data_class = carddef.data_class()
resp = get_app(pub).post_json('/api/cards/test/submit', {'data': {}}, status=403)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'unsigned API call'
def url():
signed_url = sign_url(
'http://example.net/api/cards/test/submit'
+ '?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
'1234',
)
return signed_url[len('http://example.net') :]
resp = get_app(pub).post_json(url(), {'data': {}}, status=403)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'cannot create card'
carddef.backoffice_submission_roles = [role.id]
carddef.store()
resp = get_app(pub).post_json(url(), {'data': {}})
assert resp.json['err'] == 0
assert resp.json['data']['url'] == (
'http://example.net/backoffice/data/test/%s/' % resp.json['data']['id']
)
assert resp.json['data']['backoffice_url'] == (
'http://example.net/backoffice/data/test/%s/' % resp.json['data']['id']
)
assert resp.json['data']['api_url'] == ('http://example.net/api/cards/test/%s/' % resp.json['data']['id'])
assert data_class.get(resp.json['data']['id']).status == 'wf-recorded'
assert data_class.get(resp.json['data']['id']).user_id == str(local_user.id)
assert data_class.get(resp.json['data']['id']).tracking_code is None
local_user2 = get_publisher().user_class()
local_user2.name = 'Test'
local_user2.email = 'foo@localhost'
local_user2.store()
resp = get_app(pub).post_json(url(), {'data': {}, 'user': {'NameID': [], 'email': local_user2.email}})
assert data_class.get(resp.json['data']['id']).user.email == local_user2.email
resp = get_app(pub).post(
url(), json.dumps({'data': {}}), status=400
) # missing Content-Type: application/json header
assert resp.json['err_desc'] == 'expected JSON but missing appropriate content-type'
# check qualified content type are recognized
resp = get_app(pub).post(url(), json.dumps({'data': {}}), content_type='application/json; charset=utf-8')
assert resp.json['data']['url']
def test_carddef_submit_with_varname(pub, local_user):
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
source = [{'id': '1', 'text': 'foo', 'more': 'XXX'}, {'id': '2', 'text': 'bar', 'more': 'YYY'}]
data_source.data_source = {'type': 'formula', 'value': repr(source)}
data_source.store()
data_source = NamedDataSource(name='foobar_jsonp')
data_source.data_source = {'type': 'formula', 'value': 'http://example.com/jsonp'}
data_source.store()
Role.wipe()
role = Role(name='test')
role.store()
local_user.roles = [role.id]
local_user.store()
CardDef.wipe()
carddef = CardDef()
carddef.name = 'test'
carddef.fields = [
fields.StringField(id='0', label='foobar0', varname='foobar0'),
fields.ItemField(id='1', label='foobar1', varname='foobar1', data_source={'type': 'foobar'}),
fields.ItemField(id='2', label='foobar2', varname='foobar2', data_source={'type': 'foobar_jsonp'}),
fields.DateField(id='3', label='foobar3', varname='date'),
fields.FileField(id='4', label='foobar4', varname='file'),
fields.MapField(id='5', label='foobar5', varname='map'),
fields.StringField(id='6', label='foobar6', varname='foobar6'),
]
carddef.backoffice_submission_roles = [role.id]
carddef.store()
data_class = carddef.data_class()
signed_url = sign_url(
'http://example.net/api/cards/test/submit'
+ '?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
'1234',
)
url = signed_url[len('http://example.net') :]
payload = {
'data': {
'foobar0': 'xxx',
'foobar1': '1',
'foobar1_structured': {
'id': '1',
'text': 'foo',
'more': 'XXX',
},
'foobar2': 'bar',
'foobar2_raw': '10',
'date': '1970-01-01',
'file': {
'filename': 'test.txt',
'content': force_text(base64.b64encode(b'test')),
},
'map': {
'lat': 1.5,
'lon': 2.25,
},
}
}
resp = get_app(pub).post_json(url, payload)
assert resp.json['err'] == 0
assert data_class.get(resp.json['data']['id']).status == 'wf-recorded'
assert data_class.get(resp.json['data']['id']).user_id == str(local_user.id)
assert data_class.get(resp.json['data']['id']).tracking_code is None
assert data_class.get(resp.json['data']['id']).data['0'] == 'xxx'
assert data_class.get(resp.json['data']['id']).data['1'] == '1'
assert data_class.get(resp.json['data']['id']).data['1_structured'] == source[0]
assert data_class.get(resp.json['data']['id']).data['2'] == '10'
assert data_class.get(resp.json['data']['id']).data['2_display'] == 'bar'
assert data_class.get(resp.json['data']['id']).data['3'] == time.struct_time(
(1970, 1, 1, 0, 0, 0, 3, 1, -1)
)
assert data_class.get(resp.json['data']['id']).data['4'].orig_filename == 'test.txt'
assert data_class.get(resp.json['data']['id']).data['4'].get_content() == b'test'
assert data_class.get(resp.json['data']['id']).data['5'] == '1.5;2.25'
# test bijectivity
assert (
carddef.fields[3].get_json_value(data_class.get(resp.json['data']['id']).data['3'])
== payload['data']['date']
)
for k in payload['data']['file']:
data = data_class.get(resp.json['data']['id']).data['4']
assert carddef.fields[4].get_json_value(data)[k] == payload['data']['file'][k]
assert (
carddef.fields[5].get_json_value(data_class.get(resp.json['data']['id']).data['5'])
== payload['data']['map']
)
def test_carddef_submit_from_wscall(pub, local_user):
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
source = [{'id': '1', 'text': 'foo', 'more': 'XXX'}, {'id': '2', 'text': 'bar', 'more': 'YYY'}]
data_source.data_source = {'type': 'formula', 'value': repr(source)}
data_source.store()
data_source = NamedDataSource(name='foobar_jsonp')
data_source.data_source = {'type': 'formula', 'value': 'http://example.com/jsonp'}
data_source.store()
Role.wipe()
role = Role(name='test')
role.store()
local_user.roles = [role.id]
local_user.store()
local_user2 = get_publisher().user_class()
local_user2.name = 'Jean Darmette 2'
local_user2.email = 'jean.darmette2@triffouilis.fr'
local_user2.name_identifiers = ['0123456789bis']
local_user2.store()
workflow = Workflow.get_default_workflow()
workflow.id = '2'
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
workflow.backoffice_fields_formdef.fields = [
fields.StringField(id='bo1', label='1st backoffice field', type='string', varname='backoffice_blah'),
]
workflow.store()
CardDef.wipe()
carddef = CardDef()
carddef.name = 'test'
carddef.fields = [
fields.StringField(id='0', label='foobar0', varname='foobar0'),
fields.ItemField(id='1', label='foobar1', varname='foobar1', data_source={'type': 'foobar'}),
fields.ItemField(id='2', label='foobar2', varname='foobar2', data_source={'type': 'foobar_jsonp'}),
fields.DateField(id='3', label='foobar3', varname='date'),
fields.FileField(id='4', label='foobar4', varname='file'),
fields.MapField(id='5', label='foobar5', varname='map'),
fields.StringField(id='6', label='foobar6', varname='foobar6'),
]
carddef.backoffice_submission_roles = [role.id]
carddef.workflow = workflow
carddef.store()
carddata = carddef.data_class()()
upload = PicklableUpload('test.txt', 'text/plain', 'ascii')
upload.receive([b'test'])
carddata.data = {
'0': 'xxx',
'1': '1',
'1_display': '1',
'1_structured': {
'id': '1',
'text': 'foo',
'more': 'XXX',
},
'2': '10',
'2_display': 'bar',
'3': time.strptime('1970-01-01', '%Y-%m-%d'),
'4': upload,
'5': '1.5;2.25',
'bo1': 'backoffice field',
}
carddata.just_created()
carddata.store()
def url():
signed_url = sign_url(
'http://example.net/api/cards/test/submit?orig=coucou&email=%s' % urllib.quote(local_user.email),
'1234',
)
return signed_url[len('http://example.net') :]
payload = json.loads(json.dumps(carddata.get_json_export_dict(), cls=qommon.misc.JSONEncoder))
resp = get_app(pub).post_json(url(), payload)
assert resp.json['err'] == 0
new_carddata = carddef.data_class().get(resp.json['data']['id'])
assert new_carddata.data['0'] == carddata.data['0']
assert new_carddata.data['1'] == carddata.data['1']
assert new_carddata.data['1_display'] == carddata.data['1_display']
assert new_carddata.data['1_structured'] == carddata.data['1_structured']
assert new_carddata.data['2'] == carddata.data['2']
assert new_carddata.data['2_display'] == carddata.data['2_display']
assert new_carddata.data['3'] == carddata.data['3']
assert new_carddata.data['4'].get_content() == carddata.data['4'].get_content()
assert new_carddata.data['5'] == carddata.data['5']
assert new_carddata.data['bo1'] == carddata.data['bo1']
assert not new_carddata.data.get('6')
assert new_carddata.user_id == str(local_user.id)
# add an extra attribute
payload['extra'] = {'foobar6': 'YYY'}
resp = get_app(pub).post_json(url(), payload)
assert resp.json['err'] == 0
new_carddata = carddef.data_class().get(resp.json['data']['id'])
assert new_carddata.data['0'] == carddata.data['0']
assert new_carddata.data['6'] == 'YYY'
# add user
carddata.user_id = local_user2.id
carddata.store()
payload = json.loads(json.dumps(carddata.get_json_export_dict(), cls=qommon.misc.JSONEncoder))
resp = get_app(pub).post_json(url(), payload)
assert resp.json['err'] == 0
new_carddata = carddef.data_class().get(resp.json['data']['id'])
assert str(new_carddata.user_id) == str(local_user2.id)
# test missing map data
del carddata.data['5']
payload = json.loads(json.dumps(carddata.get_json_export_dict(), cls=qommon.misc.JSONEncoder))
resp = get_app(pub).post_json(url(), payload)
assert resp.json['err'] == 0
new_carddata = carddef.data_class().get(resp.json['data']['id'])
assert new_carddata.data.get('5') is None
def test_formdef_submit_structured(pub, local_user):
Role.wipe()
role = Role(name='test')
role.store()
local_user.roles = [role.id]
local_user.store()
CardDef.wipe()
carddef = CardDef()
carddef.name = 'test'
carddef.fields = [
fields.ItemField(
id='0',
label='foobar',
varname='foobar',
data_source={
'type': 'json',
'value': 'http://datasource.com',
},
),
fields.ItemField(
id='1',
label='foobar1',
varname='foobar1',
data_source={
'type': 'formula',
'value': '[dict(id=i, text=\'label %s\' % i, foo=i) for i in range(10)]',
},
),
]
carddef.backoffice_submission_roles = [role.id]
carddef.store()
data_class = carddef.data_class()
signed_url = sign_url(
'http://example.net/api/cards/test/submit'
'?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email),
'1234',
)
url = signed_url[len('http://example.net') :]
with mock.patch('wcs.qommon.misc.urlopen') as urlopen:
urlopen.side_effect = lambda *args: StringIO(
'''\
{"data": [{"id": 0, "text": "zéro", "foo": "bar"}, \
{"id": 1, "text": "uné", "foo": "bar1"}, \
{"id": 2, "text": "deux", "foo": "bar2"}]}'''
)
resp = get_app(pub).post_json(
url,
{
'data': {
'0': '0',
"1": '3',
}
},
)
formdata = data_class.get(resp.json['data']['id'])
assert formdata.status == 'wf-recorded'
assert formdata.data['0'] == '0'
assert formdata.data['0_display'] == 'zéro'
assert formdata.data['0_structured'] == {
'id': 0,
'text': 'zéro',
'foo': 'bar',
}
assert formdata.data['1'] == '3'
assert formdata.data['1_display'] == 'label 3'
assert formdata.data['1_structured'] == {
'id': 3,
'text': 'label 3',
'foo': 3,
}