# -*- 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, }