import base64 import json import os import re import shutil import mock from django.core.files.storage import default_storage from django.core.urlresolvers import reverse from django.conf import settings from django.contrib.auth.models import User, Group from django.template import TemplateSyntaxError from django.test import override_settings from django.utils.http import urlencode from django.utils.six import BytesIO from django.utils.six.moves.urllib import parse as urlparse import pytest from webtest import TestApp from webtest import Upload from combo.wsgi import application from combo.data.models import Page, CellBase, TextCell, LinkCell, ConfigJsonCell, JsonCell, PageSnapshot from combo.apps.assets.models import Asset from combo.apps.family.models import FamilyInfosCell from combo.apps.search.models import SearchCell pytestmark = pytest.mark.django_db TESTS_DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') def login(app, username='admin', password='admin'): login_page = app.get('/login/') login_form = login_page.forms[0] login_form['username'] = username login_form['password'] = password resp = login_form.submit() assert resp.status_int == 302 return app def test_unlogged_access(app): # connect while not being logged in assert app.get('/manage/', status=302).location.endswith('/login/?next=/manage/') def test_access(app, admin_user): app = login(app) resp = app.get('/manage/', status=200) assert 'Pages' in resp.text assert "This site doesn't have any page yet." in resp.text def test_add_page(app, admin_user): app = login(app) resp = app.get('/manage/', status=200) resp = resp.click('New') assert resp.forms[0]['title'].value == 'Home' # default title for first page resp = resp.forms[0].submit() assert resp.location.endswith('/manage/pages/1/') assert Page.objects.get(slug='index').title == 'Home' assert Page.objects.get(slug='index').template_name == 'standard' # first one was taken def test_add_second_page(app, admin_user): Page.objects.all().delete() page = Page(title='One', slug='one') page.save() app = login(app) resp = app.get('/manage/', status=200) resp = resp.click('New') # assert there's no defaul title for the second page assert resp.forms[0]['title'].value == '' resp.forms[0]['title'].value = 'Foobar' resp = resp.forms[0].submit() assert Page.objects.get(slug='foobar').title == 'Foobar' def test_edit_page(app, admin_user): Page.objects.all().delete() app = login(app) resp = app.get('/manage/', status=200) resp = resp.click('New') resp = resp.forms[0].submit() resp = resp.follow() # title resp = resp.click(href='.*/title') resp.form['title'].value = 'Home 2' resp = resp.form.submit() resp = resp.follow() assert Page.objects.all()[0].title == 'Home 2' # slug resp = resp.click(href='.*/slug') resp.form['slug'].value = 'foobar' resp = resp.form.submit() resp = resp.follow() assert Page.objects.all()[0].slug == 'foobar' # description resp = resp.click(href='.*/description') resp.form['description'].value = 'second home page' resp = resp.form.submit() resp = resp.follow() assert Page.objects.all()[0].description == 'second home page' # template name resp = resp.click(href='.*/template') assert resp.form['template_name'].value == 'standard' assert len(resp.form['template_name'].options) == 2 # by default there are 2 templates resp.form['template_name'].value = 'standard-sidebar' resp = resp.form.submit() resp = resp.follow() assert Page.objects.all()[0].template_name == 'standard-sidebar' assert resp.click(href='.*/template').form['template_name'].value == 'standard-sidebar' # redirection resp = resp.click(href='.*/redirection') resp.form['redirect_url'].value = 'http://www.example.net' resp = resp.form.submit() resp = resp.follow() assert 'http://www.example.net' in resp.text assert Page.objects.all()[0].redirect_url == 'http://www.example.net' # exclude from nav resp = resp.click(href='.*/exclude-from-navigation') resp.form['exclude_from_navigation'].checked = True resp = resp.form.submit() resp = resp.follow() assert Page.objects.all()[0].exclude_from_navigation is True def test_edit_page_unknown_template(app, admin_user): Page.objects.all().delete() page = Page(title='One', slug='one', template_name='broken') page.save() app = login(app) resp = app.get('/manage/pages/%s/' % page.id) assert 'Unknown (broken)' in resp.text def test_page_edit_slug(app, admin_user): Page.objects.all().delete() page = Page(title='One', slug='one', template_name='standard') page.save() page = Page(title='Two', slug='two', template_name='standard') page.save() app = login(app) resp = app.get('/manage/pages/%s/' % page.id) resp = resp.click(href='.*/slug') resp.form['slug'].value = 'two' # no change resp = resp.form.submit() resp = resp.follow() assert Page.objects.get(id=page.id).slug == 'two' resp = resp.click(href='.*/slug') resp.form['slug'].value = 'two2' # valid change resp = resp.form.submit() resp = resp.follow() assert Page.objects.get(id=page.id).slug == 'two2' resp = resp.click(href='.*/slug') resp.form['slug'].value = 'one' # duplicate resp = resp.form.submit() assert 'Slug must be unique' in resp.text assert Page.objects.get(id=page.id).slug == 'two2' def test_page_edit_picture(app, admin_user): Page.objects.all().delete() page = Page(title='One', slug='one', template_name='standard') page.save() app = login(app) resp = app.get('/manage/pages/%s/' % page.id) resp = resp.click(href='.*/picture/') resp.form['picture'] = Upload('black.jpeg', open(os.path.join(TESTS_DATA_DIR, 'black.jpeg'), mode='rb').read(), 'image/jpeg') resp = resp.form.submit() assert resp.location.endswith('/manage/pages/%s/' % page.id) resp = resp.follow() assert '

Page - One

' in resp.text assert 'Page - One' in resp.text assert not 'Delete' in resp.text resp = resp.forms[0].submit() assert resp.location.endswith('/manage/') assert Page.objects.count() == 0 def test_delete_page_keep_child(app, admin_user): Page.objects.all().delete() page = Page(title='One', slug='one', template_name='standard') page.save() page2 = Page(title='Two', slug='two', parent=page, template_name='standard') page2.save() app = login(app) resp = app.get('/manage/pages/%s/' % page.id) resp = resp.click('delete') assert '' in resp.text assert 'Delete only this page' in resp.text assert 'Delete this page and all subpages' in resp.text resp.form['choice'].value = 'delete-one' resp = resp.forms[0].submit() assert resp.location.endswith('/manage/') assert Page.objects.count() == 1 assert Page.objects.get(id=page2.id) == page2 def test_delete_page_and_subpage(app, admin_user): Page.objects.all().delete() page = Page(title='One', slug='one', template_name='standard') page.save() page2 = Page(title='Two', slug='two', parent=page, template_name='standard') page2.save() app = login(app) resp = app.get('/manage/pages/%s/' % page.id) resp = resp.click('delete') assert '' in resp.text assert 'Delete only this page' in resp.text assert 'Delete this page and all subpages' in resp.text resp.form['choice'].value = 'delete-all' resp = resp.forms[0].submit() assert resp.location.endswith('/manage/') assert Page.objects.count() == 0 def test_page_reorder(app, admin_user): Page.objects.all().delete() page1 = Page(title='One', slug='one', parent=None, order=0, template_name='standard') page1.save() page2 = Page(title='Two', slug='two', parent=None, order=1, template_name='standard') page2.save() page3 = Page(title='Three', slug='three', parent=page2, order=2, template_name='standard') page3.save() page4 = Page(title='Four', slug='four', parent=page2, order=3, template_name='standard') page4.save() ordered_ids = [x.id for x in Page.get_as_reordered_flat_hierarchy(Page.objects.all())] ordered_ids = [page1.id, page2.id, page3.id, page4.id] # move page4 before page3 app = login(app) resp = app.get('/manage/pages/order', params={ 'moved-page-id': page4.id, 'moved-page-new-parent': page2.id, 'new-order': ','.join([str(x) for x in [page1.id, page2.id, page4.id, page3.id]])}) ordered_ids = [x.id for x in Page.get_as_reordered_flat_hierarchy(Page.objects.all())] ordered_ids = [page1.id, page2.id, page4.id, page3.id] # move page4 to level0 resp = app.get('/manage/pages/order', params={ 'moved-page-id': page4.id, 'moved-page-new-parent': '', 'new-order': ','.join([str(x) for x in [page1.id, page4.id, page2.id, page3.id]])}) ordered_ids = [x.id for x in Page.get_as_reordered_flat_hierarchy(Page.objects.all())] ordered_ids = [page1.id, page4.id, page2.id, page3.id] # change slug to check for autochange on duplicate page4.slug = 'three' page4.save() # move it as a sibling of page3 resp = app.get('/manage/pages/order', params={ 'moved-page-id': page4.id, 'moved-page-new-parent': page2.id, 'new-order': ','.join([str(x) for x in [page1.id, page2.id, page4.id, page3.id]])}) assert Page.objects.get(id=page4.id).slug.startswith('three-') def test_export_page(app, admin_user): Page.objects.all().delete() page = Page(title='One', slug='one', template_name='standard') page.save() app = login(app) resp = app.get('/manage/pages/%s/' % page.id) resp = resp.click('export') assert resp.headers['content-type'] == 'application/json' assert resp.json['pages'][0].get('fields').get('slug') == 'one' def test_export_page_order(): Page.objects.all().delete() page1 = Page(title='One', slug='one', template_name='standard') page2 = Page(title='Two', slug='two', parent=page1, template_name='standard') page3 = Page(title='Three', slug='three', parent=page2, template_name='standard') page4 = Page(title='Four', slug='four', parent=page1, template_name='standard') random_list = [page3, page4, page1, page2] ordered_list = Page.get_as_reordered_flat_hierarchy(random_list) assert ordered_list[0] == page1 assert ordered_list[1] == page4 assert ordered_list[2] == page2 assert ordered_list[3] == page3 def test_site_export_import(app, admin_user): Page.objects.all().delete() page1 = Page(title='One', slug='one', template_name='standard') page1.save() page2 = Page(title='Two', slug='two', parent=page1, template_name='standard') page2.save() page3 = Page(title='Three', slug='three', parent=page2, template_name='standard') page3.save() page4 = Page(title='Four', slug='four', parent=page1, template_name='standard') page4.save() cell = TextCell(page=page1, placeholder='content', text='Foobar', order=0) cell.save() cell = LinkCell(page=page1, placeholder='content', link_page=page2, order=0) cell.save() cell = LinkCell(page=page2, placeholder='content', link_page=page1, order=0) cell.save() app = login(app) resp = app.get('/manage/') resp = resp.click('Export Site') assert resp.headers['content-type'] == 'application/json' site_export = resp.body Page.objects.all().delete() assert LinkCell.objects.count() == 0 app = login(app) resp = app.get('/manage/') resp = resp.click('Import Site') resp.form['site_json'] = Upload('site-export.json', site_export, 'application/json') resp = resp.form.submit() assert Page.objects.count() == 4 assert LinkCell.objects.count() == 2 assert LinkCell.objects.get(page__slug='one').link_page.slug == 'two' assert LinkCell.objects.get(page__slug='two').link_page.slug == 'one' # check with invalid file resp = app.get('/manage/') resp = resp.click('Import Site') resp.form['site_json'] = Upload('site-export.json', b'invalid content', 'application/json') resp = resp.form.submit() assert 'File is not in the expected JSON format.' in resp.text def test_add_edit_cell(app, admin_user): Page.objects.all().delete() page = Page(title='One', slug='one', template_name='standard') page.save() app = login(app) resp = app.get('/manage/pages/%s/' % page.id) # click on first option link, this should add a text cell resp = app.get(resp.html.find('option').get('data-add-url')) assert resp.location.endswith('/manage/pages/1/') cells = CellBase.get_cells(page_id=page.id) assert len(cells) == 1 assert isinstance(cells[0], TextCell) resp = app.get('/manage/pages/%s/' % page.id) assert ('data-cell-reference="%s"' % cells[0].get_reference()) in resp.text resp.forms[0]['c%s-text' % cells[0].get_reference()].value = 'Hello world' resp = resp.forms[0].submit() assert resp.status_int == 302 assert resp.location.endswith('/manage/pages/1/') resp = app.get('/manage/pages/%s/' % page.id) assert resp.forms[0]['c%s-text' % cells[0].get_reference()].value == 'Hello world' resp = app.get('/manage/pages/%s/' % page.id) assert ('data-cell-reference="%s"' % cells[0].get_reference()) in resp.text resp.forms[0]['c%s-text' % cells[0].get_reference()].value = 'World Hello' resp = resp.forms[0].submit(xhr=True) assert resp.status_int == 200 assert resp.text.strip().startswith('

Banner<' in resp.text assert '>Delete<' not in resp.text resp = resp.click('Overwrite') resp.form['upload'] = Upload('test.png', base64.decodestring(b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAgAABAADRWoApgAA\nAABJRU5ErkJggg=='), 'image/png') resp = resp.form.submit() assert resp.location.endswith('?page=1') resp = resp.follow() assert 'test.png' in resp.text assert '>Delete<' in resp.text assert Asset.objects.filter(key='collectivity:banner').count() == 1 assert app.get('/assets/collectivity:banner', status=302)['location'].endswith('test.png') # upload a new version of image resp = resp.click('Overwrite') resp.form['upload'] = Upload('test2.png', base64.decodestring(b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAgAABAADRWoApgAA\nAABJRU5ErkJggg=='), 'image/png') resp = resp.form.submit().follow() assert 'test2.png' in resp.text assert 'Delete<' in resp.text assert Asset.objects.filter(key='collectivity:banner').count() == 1 resp = resp.click('Delete') resp = resp.form.submit() assert resp.location.endswith('?page=1') resp = resp.follow() assert '>Banner<' in resp.text assert '>Delete<' not in resp.text assert Asset.objects.filter(key='collectivity:banner').count() == 0 resp = app.get('/assets/collectivity:banner', status=404) with override_settings(COMBO_ASSET_SLOTS={'collectivity:cgu': {'label': 'CGU', 'asset-type': 'document'}}): resp = app.get('/manage/assets/') assert '>CGU<' in resp.text assert '>Delete<' not in resp.text resp = resp.click('Overwrite') resp.form['upload'] = Upload('test.pdf', 'PDF-1.4 ...', 'application.pdf') resp = resp.form.submit().follow() assert 'test.pdf' in resp.text assert 'Delete<' in resp.text assert Asset.objects.filter(key='collectivity:cgu').count() == 1 def test_asset_export_import(app, admin_user): for path in ('uploads', 'assets', 'cache'): if os.path.exists(default_storage.path(path)): shutil.rmtree(default_storage.path(path)) app = login(app) # upload a file resp = app.get('/manage/assets/') resp = resp.click('Upload') resp.form['upload'] = Upload('test.png', base64.decodestring(b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg=='), 'image/png') resp = resp.form.submit() resp = app.get('/manage/assets/') resp = resp.click('Export') assert resp.content_type == 'application/x-tar' content = resp.content for path in ('uploads', 'assets'): if os.path.exists(default_storage.path(path)): shutil.rmtree(default_storage.path(path)) resp = app.get('/manage/assets/') assert 'have any asset yet.' in resp.text resp = resp.click('Import') resp.form['assets_file'] = Upload('test.tar', content) resp = resp.form.submit() assert sum([len(x[2]) for x in os.walk(default_storage.path(''))]) == 2 resp = resp.follow() assert 'The assets file has been imported.' in resp.text # test no overwrite filename = re.findall('data-href="(.*?)"', resp.text)[0][7:] # strip /media/ with open(default_storage.path(filename), 'w') as fd: fd.write('test') # 4 bytes assert os.stat(default_storage.path(filename)).st_size == 4 resp = app.get('/manage/assets/') resp = resp.click('Import') resp.form['assets_file'] = Upload('test.tar', content) resp = resp.form.submit() resp = resp.follow() assert 'The assets file has been imported.' in resp.text assert os.stat(default_storage.path(filename)).st_size == 4 # test overwrite resp = app.get('/manage/assets/') resp = resp.click('Import') resp.form['overwrite'] = True resp.form['assets_file'] = Upload('test.tar', content) resp = resp.form.submit() resp = resp.follow() assert 'The assets file has been imported.' in resp.text assert os.stat(default_storage.path(filename)).st_size == 67 # test uploading garbage for path in ('uploads', 'assets'): if os.path.exists(default_storage.path(path)): shutil.rmtree(default_storage.path(path)) resp = app.get('/manage/assets/') resp = resp.click('Import') resp.form['assets_file'] = Upload('test.tar', b'garbage') resp = resp.form.submit() assert sum([len(x[2]) for x in os.walk(default_storage.path(''))]) == 0 resp = resp.follow() assert 'The assets file is not valid.' in resp.text def test_menu_json(app, admin_user): app.get('/manage/menu.json', status=302) app = login(app) resp = app.get('/manage/menu.json') assert resp.headers['content-type'] == 'application/json' assert resp.json[0]['label'] == 'Content Management' resp = app.get('/manage/menu.json?callback=fooBar') assert resp.headers['content-type'] == 'application/javascript' assert resp.text.startswith('fooBar([{"') def test_page_multiple_link_cells(app, admin_user): Page.objects.all().delete() page = Page(title='One', slug='one', template_name='standard') page.save() for i in range(10): Page(title='page %d' % i, slug='page%d' % i, template_name='standard').save() cell = LinkCell(page=page, placeholder='content', order=0) cell.save() patched_orig = Page.get_as_reordered_flat_hierarchy app = login(app) with mock.patch('combo.data.models.Page.get_as_reordered_flat_hierarchy') as func: func.return_value = [] resp = app.get('/manage/pages/%s/' % page.id) assert func.call_count == 1 def test_page_cell_placeholder(app, admin_user): page = Page(title='One', slug='one', template_name='standard') page.save() cell = JsonCell(page=page, placeholder='content', order=0, url='http://example.com') cell.save() app = login(app) resp = app.get('/manage/pages/%s/' % page.id) assert re.findall('data-placeholder-key="(.*)">', resp.text) == ['content', 'footer'] # check a placeholder within a cell is included cell.template_string = '{% load combo %}{% placeholder "foobar" name="Foobar" %}' cell.save() resp = app.get('/manage/pages/%s/' % page.id) assert re.findall('data-placeholder-key="(.*)">', resp.text) == ['content', 'foobar', 'footer'] assert re.findall('

(.*)

', resp.text) == [ 'Page - One', 'Content', 'JSON Feed / Foobar', 'Footer'] def test_page_familycell_placeholder(app, admin_user): page = Page(title='My family', slug='my-family', template_name='standard') page.save() cell = FamilyInfosCell(page=page, placeholder='content', order=0) cell.save() app = login(app) resp = app.get('/manage/pages/%s/' % page.id) assert re.findall('data-placeholder-key="(.*)">', resp.text) == ['content', "family_unlinked_user", "footer"] assert re.findall('

(.*)

', resp.text) == [ 'Page - My family', 'Content', 'Family Information Cell / Top content for unlinked users', 'Footer'] def test_page_discover_placeholder_with_error_cells(app, admin_user): page = Page(title='One', slug='one', template_name='standard') page.save() cell = JsonCell(page=page, placeholder='content', order=0, url='xx') cell.template_string = '{% xxx %}' cell.save() app = login(app) with mock.patch('combo.utils.requests.get') as requests_get: data = {'data': [{'url': 'xxx', 'text': 'xxx'}]} with pytest.raises(TemplateSyntaxError): cell.render({}) resp = app.get('/manage/pages/%s/' % page.id) assert re.findall('data-placeholder-key="(.*)">', resp.text) == ['content', 'footer'] def test_page_versionning(app, admin_user): Page.objects.all().delete() PageSnapshot.objects.all() page = Page(title='One', slug='one') page.save() cell1 = TextCell(page=page, placeholder='content', text='Foobar1', order=0) cell1.save() cell2 = TextCell(page=page, placeholder='content', text='Foobar2', order=1) cell2.save() cell3 = TextCell(page=page, placeholder='content', text='Foobar3', order=1) cell3.save() anonymous_app = app app = login(app) resp = app.get('/manage/pages/%s/' % page.id, status=200) # update title resp = resp.click(href='.*/title') resp.form['title'].value = 'One Two' resp = resp.form.submit() resp = resp.follow() assert Page.objects.all()[0].title == 'One Two' assert PageSnapshot.objects.all().count() == 1 # change cell text resp.forms[0]['c%s-text' % cell1.get_reference()].value = 'Hello world' resp = resp.forms[0].submit().follow() assert PageSnapshot.objects.all().count() == 2 # reorder cells params = [] for i, cell in enumerate([cell3, cell1, cell2]): params.append(('ph_data_textcell-%s' % cell.id, 'content')) # no placeholder change params.append(('pos_data_textcell-%s' % cell.id, str(i))) app.get('/manage/pages/%s/order?%s' % (page.id, urlencode(params))) assert PageSnapshot.objects.all().count() == 3 resp = resp.click('history') assert resp.text.index('reordered cells') < resp.text.index('changed cell') < resp.text.index('changed title') resp2 = resp.click('view', index=1) assert resp2.text.index('Hello world') < resp2.text.index('Foobar3') resp2 = resp.click('view', index=0) assert resp2.text.index('Hello world') > resp2.text.index('Foobar3') resp2 = resp.click('view', index=2) assert 'Foobar1' in resp2.text assert not 'Hello world' in resp2.text assert Page.objects.all().count() == 1 assert PageSnapshot.objects.filter(page=page)[0].user.username == 'admin' # check with asynchronous cells resp = app.get('/manage/pages/%s/add-cell-to-content/data_jsoncell/default/' % page.id) resp = resp.follow() resp.forms[3]['cdata_jsoncell-1-template_string'].value = 'A{{json.data.0.text}}B' resp.forms[3]['cdata_jsoncell-1-url'].value = 'http://example.com' resp = resp.forms[3].submit().follow() assert PageSnapshot.objects.all().count() == 5 # add + change resp.forms[3]['cdata_jsoncell-1-template_string'].value = 'C{{json.data.0.text}}D' resp = resp.forms[3].submit().follow() assert PageSnapshot.objects.all().count() == 6 resp.forms[1]['c%s-text' % cell1.get_reference()].value = 'Foo back to 1' resp = resp.forms[0].submit().follow() resp = resp.click('history') assert 'added cell' in resp.text resp2 = resp.click('view', index=1) json_cell_url = re.findall(r'/ajax/cell/.*/data_jsoncell-.*/', resp2.text)[0] with mock.patch('combo.utils.requests.get') as requests_get: data = {'data': [{'url': 'xxx', 'text': 'xxx'}]} requests_get.return_value = mock.Mock(content=json.dumps(data), status_code=200) resp3 = app.get(json_cell_url) assert resp3.text.strip() == 'CxxxD' # previous version should return AxxxB resp2 = resp.click('view', index=2) json_cell_url = re.findall(r'/ajax/cell/.*/data_jsoncell-.*/', resp2.text)[0] with mock.patch('combo.utils.requests.get') as requests_get: data = {'data': [{'url': 'xxx', 'text': 'xxx'}]} requests_get.return_value = mock.Mock(content=json.dumps(data), status_code=200) resp3 = app.get(json_cell_url) assert resp3.text.strip() == 'AxxxB' # check anonymous users can't get to cells from snapshots app.get('/logout/') resp3 = app.get(json_cell_url, status=403) # restore a snapshot app = login(app) resp = resp.click('restore', index=6) resp = resp.form.submit() resp = resp.follow() resp2 = resp.click('see online') assert resp2.text.index('Foobar1') < resp2.text.index('Foobar2') < resp2.text.index('Foobar3') # clean it up Page.snapshots.all().delete() assert Page.objects.count() == 1 assert TextCell.objects.count() == 3 assert JsonCell.objects.count() == 0 def test_django_admin(app, admin_user): resp = app.get('/admin/') resp = resp.follow() # -> /admin/login/ resp = resp.follow() # -> /login/ resp.form['username'] = 'admin' resp.form['password'] = 'admin' resp = resp.form.submit() resp = resp.follow() # -> /admin/ assert '/admin/logout/' in resp.text resp = resp.click(href='/admin/logout/') resp = resp.follow() # -> /logout/ assert urlparse.urlparse(resp.location).path == '/'