combo/tests/test_public.py

630 lines
26 KiB
Python

import mock
from webtest import TestApp
import datetime
import json
import pytest
import os
import urllib
from urlparse import urlparse
from django.conf import settings
from django.core.urlresolvers import reverse
from django.db import connection
from django.test import override_settings
from django.test.utils import CaptureQueriesContext
from combo.wsgi import application
from combo.data.models import (Page, CellBase, TextCell, ParentContentCell,
FeedCell, LinkCell, ConfigJsonCell, Redirect)
from combo.apps.family.models import FamilyInfosCell
pytestmark = pytest.mark.django_db
from test_manager import admin_user, login
def test_missing_index(app):
Page.objects.all().delete()
resp = app.get('/', status=200)
assert 'Welcome' in resp.body
def test_index(app):
Page.objects.all().delete()
page = Page(title='Home', slug='index', template_name='standard')
page.save()
resp = app.get('/index', status=301)
assert urlparse(resp.location).path == '/'
resp = app.get('/', status=200)
# check {% now %} inside a skeleton_extra_placeholder is interpreted
assert str(datetime.datetime.now().year) in resp.body
def test_page_contents(app):
Page.objects.all().delete()
page = Page(title='Home', slug='index', template_name='standard')
page.save()
cell = TextCell(page=page, placeholder='content', text='Foobar', order=0)
cell.save()
resp = app.get('/', status=200)
assert 'Foobar' in resp.body
def test_page_contents_unlogged_only(app, admin_user):
Page.objects.all().delete()
page = Page(title='Home', slug='index', template_name='standard')
page.save()
cell = TextCell(page=page, placeholder='content', text='Foobar', order=0,
restricted_to_unlogged=True)
cell.save()
resp = app.get('/', status=200)
assert 'Foobar' in resp.body
app = login(app)
resp = app.get('/', status=200)
assert not 'Foobar' in resp.body
def test_page_footer_acquisition(app):
Page.objects.all().delete()
page = Page(title='Home', slug='index', template_name='standard')
page.save()
page_index = page
cell = TextCell(page=page, placeholder='footer', text='BARFOO', order=0)
cell.save()
resp = app.get('/', status=200)
assert resp.body.count('BARFOO') == 1
# make sure a parent content cell in the home page doesn't duplicate
# cells
ParentContentCell(page=page, placeholder='footer', order=0).save()
resp = app.get('/', status=200)
assert resp.body.count('BARFOO') == 1
page = Page(title='Second', slug='second', template_name='standard')
page.save()
ParentContentCell(page=page, placeholder='footer', order=0).save()
TextCell(page=page, placeholder='footer', text='BAR2FOO', order=1).save()
resp = app.get('/second', status=301)
assert urlparse(resp.location).path == '/second/'
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/second/', status=200)
assert resp.body.count('BARFOO') == 1
assert resp.body.count('BAR2FOO') == 1
queries_count_second = len(ctx.captured_queries)
# deeper hierarchy
page3 = Page(title='Third', slug='third', template_name='standard', parent=page)
page3.save()
ParentContentCell(page=page3, placeholder='footer', order=0).save()
page4 = Page(title='Fourth', slug='fourth', template_name='standard', parent=page3)
page4.save()
ParentContentCell(page=page4, placeholder='footer', order=0).save()
page5 = Page(title='Fifth', slug='fifth', template_name='standard', parent=page4)
page5.save()
ParentContentCell(page=page5, placeholder='footer', order=0).save()
# check growth in SQL queries is limited to an additional page lookup
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/second/third/', status=200)
assert resp.body.count('BARFOO') == 1
assert resp.body.count('BAR2FOO') == 1
queries_count_third = len(ctx.captured_queries)
assert queries_count_third == queries_count_second + 1
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/second/third/fourth/', status=200)
assert resp.body.count('BARFOO') == 1
assert resp.body.count('BAR2FOO') == 1
queries_count_fourth = len(ctx.captured_queries)
assert queries_count_fourth == queries_count_second + 2
# check footer doesn't get duplicated in real index children
page6 = Page(title='Sixth', slug='sixth', template_name='standard', parent=page_index)
page6.save()
ParentContentCell(page=page6, placeholder='footer', order=0).save()
resp = app.get('/sixth/', status=200)
assert resp.body.count('BARFOO') == 1
def test_page_redirect(app):
Page.objects.all().delete()
page = Page(title='Elsewhere', slug='elsewhere', template_name='standard',
redirect_url='http://example.net')
page.save()
resp = app.get('/elsewhere/', status=302)
assert resp.location == 'http://example.net'
def test_page_templated_redirect(app):
Page.objects.all().delete()
with override_settings(TEMPLATE_VARS={'test_url': 'http://example.net'}):
page = Page(title='Elsewhere', slug='elsewhere', template_name='standard',
redirect_url='[test_url]')
page.save()
resp = app.get('/elsewhere/', status=302)
assert resp.location == 'http://example.net'
def test_page_private_unlogged(app):
Page.objects.all().delete()
page = Page(title='Home', slug='index', template_name='standard', public=False)
page.save()
resp = app.get('/', status=302)
assert resp.location.endswith('/login/?next=http%3A//testserver/')
def test_page_private_logged_in(app, admin_user):
Page.objects.all().delete()
page = Page(title='Home', slug='index', template_name='standard', public=False)
page.save()
app = login(app)
resp = app.get('/', status=200)
def test_page_skeleton(app):
Page.objects.all().delete()
page = Page(title='Elsewhere', slug='elsewhere', template_name='standard',
redirect_url='http://example.net/foo/')
page.save()
# url prefix match
resp = app.get('/__skeleton__/?source=%s' % urllib.quote('http://example.net/foo/bar'))
assert '{% block placeholder-content %}{% block content %}{% endblock %}{% endblock %}' in resp.body
assert '{% block placeholder-footer %}{% block footer %}{% endblock %}{% endblock %}' in resp.body
# url netloc match
resp = app.get('/__skeleton__/?source=%s' % urllib.quote('http://example.net'))
assert '{% block placeholder-content %}{% block content %}{% endblock %}{% endblock %}' in resp.body
assert '{% block placeholder-footer %}{% block footer %}{% endblock %}{% endblock %}' in resp.body
# settings.KNOWN_SERVICES match
resp = app.get('/__skeleton__/?source=%s' % urllib.quote('http://127.0.0.1:8999/'))
assert '{% block placeholder-content %}{% block content %}{% endblock %}{% endblock %}' in resp.body
# no match
resp = app.get('/__skeleton__/?source=%s' % urllib.quote('http://example.com/foo/bar'), status=403)
# check with a footer cell
cell = TextCell(page=page, placeholder='footer', text='Foobar', order=0)
cell.save()
resp = app.get('/__skeleton__/?source=%s' % urllib.quote('http://example.net'))
assert '{% block placeholder-content %}{% block content %}{% endblock %}{% endblock %}' in resp.body
assert not '{% block placeholder-footer %}{% block footer %}{% endblock %}{% endblock %}' in resp.body
assert 'Foobar' in resp.body
# check {% now %} inside a skeleton_extra_placeholder is not interpreted
assert '{%now' in resp.body
# check cells in footer are present even if there's no redirection page
page.slug = 'index'
page.save()
resp = app.get('/__skeleton__/?source=%s' % urllib.quote('http://127.0.0.1:8999/'))
assert '{% block placeholder-content %}{% block content %}{% endblock %}{% endblock %}' in resp.body
assert not '{% block placeholder-footer %}{% block footer %}{% endblock %}{% endblock %}' in resp.body
assert 'Foobar' in resp.body
# check link cells provide a full URL
other_page = Page(title='Plop', slug='plop', template_name='standard')
other_page.save()
cell = LinkCell(page=page, placeholder='footer', link_page=other_page, order=0)
cell.save()
resp = app.get('/__skeleton__/?source=%s' % urllib.quote('http://127.0.0.1:8999/'))
assert 'http://testserver/plop' in resp.body
# add a bad redirection page (don't use it, do not crash)
page = Page(title='BadRedirection', slug='badredir', template_name='standard',
redirect_url='[foo_bar]')
page.save()
resp = app.get('/__skeleton__/?source=%s' % urllib.quote('http://example.net/foo/bar'))
resp = app.get('/__skeleton__/?source=%s' % urllib.quote('http://example.net/badredir'))
def test_subpage_location(app):
Page.objects.all().delete()
page_index = Page(title='Home Page', slug='index', template_name='standard')
page_index.save()
page_sibling = Page(title='Second top level page', slug='second', template_name='standard')
page_sibling.save()
page = Page(title='Child of Home', slug='child-home',
template_name='standard', parent=page_index)
page.save()
page = Page(title='Grand child of home', slug='grand-child-home',
template_name='standard', parent=page)
page.save()
page = Page(title='Child of second', slug='child-second',
template_name='standard', parent=page_sibling)
page.save()
page = Page(title='Grand child of second', slug='grand-child-second',
template_name='standard', parent=page)
page.save()
resp = app.get('/', status=200)
assert 'Home Page' in resp.body
resp = app.get('/child-home/', status=200)
assert 'Child of Home' in resp.body
resp = app.get('/child-home/grand-child-home/', status=200)
assert 'Grand child of home' in resp.body
assert urlparse(app.get('/child-home/grand-child-home', status=301).location
).path == '/child-home/grand-child-home/'
app.get('/grand-child-home/', status=404)
resp = app.get('/second/', status=200)
assert 'Second top level page' in resp.body
resp = app.get('/second/child-second/', status=200)
assert 'Child of second' in resp.body
resp = app.get('/second/child-second/grand-child-second/', status=200)
assert 'Grand child of second' in resp.body
def test_404(app):
Page.objects.all().delete()
resp = app.get('/foobar/', status=404)
assert "This page doesn't exist" in resp.body
page = Page(title='Not Found', slug='404', template_name='standard')
page.save()
TextCell(page=page, placeholder='content', text='Custom 404 Text', order=0).save()
resp = app.get('/foobar/', status=404)
assert 'Custom 404 Text' in resp.body
def test_style_demo(app, admin_user):
TextCell.objects.all().delete()
Page.objects.all().delete()
app = login(app)
with override_settings(DEBUG=True):
resp = app.get('/__style__/', status=200)
assert 'Lorem ipsum' in resp.body
assert TextCell.objects.count() == 0
assert Page.objects.count() == 0
def test_page_async_cell(app):
Page.objects.all().delete()
page = Page(title='Home', slug='index', template_name='standard')
page.save()
cell = FeedCell(page=page, placeholder='content', url='http://example.net',
order=1)
cell.save()
resp = app.get('/', status=200)
assert 'data-ajax-cell-must-load="true"' in resp.body
def test_ajax_cell(app):
Page.objects.all().delete()
page = Page(title='Home', slug='index', template_name='standard')
page.save()
page2 = Page(title='Other', slug='other', template_name='standard')
page2.save()
cell = TextCell(page=page, placeholder='content', text='<p>Foobar</p>', order=0)
cell.save()
resp = app.get(reverse('combo-public-ajax-page-cell',
kwargs={'page_pk': page.id, 'cell_reference': cell.get_reference()}))
assert resp.body.strip() == '<p>Foobar</p>'
resp = app.get(reverse('combo-public-ajax-page-cell',
kwargs={'page_pk': page2.id, 'cell_reference': cell.get_reference()}),
status=404)
resp = app.get(reverse('combo-public-ajax-page-cell',
kwargs={'page_pk': '100', 'cell_reference': cell.get_reference()}),
status=404)
page.public = False
page.save()
resp = app.get(reverse('combo-public-ajax-page-cell',
kwargs={'page_pk': page.id, 'cell_reference': cell.get_reference()}),
status=403)
page.public = True
page.save()
cell.public = False
cell.save()
resp = app.get('/', status=200)
assert not 'FOOBAR' in resp.body
resp = app.get(reverse('combo-public-ajax-page-cell',
kwargs={'page_pk': page.id, 'cell_reference': cell.get_reference()}),
status=403)
def test_initial_login_page(app, admin_user):
Page.objects.all().delete()
page = Page(title='Home', slug='index', template_name='standard')
page.save()
page = Page(title='Initial Login', slug='initial-login', template_name='standard', public=False)
page.save()
with override_settings(COMBO_INITIAL_LOGIN_PAGE_PATH='/initial-login/'):
resp = app.get('/', status=200)
# first visit
app = login(app)
resp = app.get('/', status=302)
assert urlparse(resp.location).path == '/initial-login/'
# visit again
resp = app.get('/', status=200)
def test_welcome_page(app, admin_user):
Page.objects.all().delete()
page = Page(title='Home', slug='index', template_name='standard')
page.save()
page = Page(title='Welcome', slug='welcome', template_name='standard')
page.save()
with override_settings(COMBO_WELCOME_PAGE_PATH='/welcome/'):
app.cookiejar.clear()
resp = app.get('/', status=302)
assert urlparse(resp.location).path == '/welcome/'
resp = app.get('/', status=200)
app.cookiejar.clear()
resp = app.get('/', status=302)
assert urlparse(resp.location).path == '/welcome/'
app.cookiejar.clear()
app = login(app)
resp = app.get('/', status=200)
def test_post_cell(app):
Page.objects.all().delete()
page = Page(title='Home', slug='index', template_name='standard')
page.save()
cell = TextCell(page=page, placeholder='content', text='<p>Foobar</p>', order=0)
cell.save()
# check it's not possible to post to cell that doesn't have POST support.
resp = app.post(reverse('combo-public-ajax-page-cell',
kwargs={'page_pk': page.id, 'cell_reference': cell.get_reference()}),
params={'hello': 'world'},
status=403)
templates_settings = [settings.TEMPLATES[0].copy()]
templates_settings[0]['DIRS'] = ['%s/templates-1' % os.path.abspath(os.path.dirname(__file__))]
with override_settings(JSON_CELL_TYPES={
'test-post-cell': {
'name': 'Foobar',
'url': 'http://test-post-cell/',
'actions': {
'create': {
'url': 'http://test-post-cell/create/',
},
'update': {
'url': 'http://test-post-cell/update/',
'method': 'PATCH',
}
}
}},
TEMPLATES=templates_settings
):
cell = ConfigJsonCell(page=page, placeholder='content', order=0)
cell.key = 'test-post-cell'
cell.save()
with mock.patch('combo.utils.requests.get') as requests_get:
requests_get.return_value = mock.Mock(content=json.dumps({'hello': 'world'}), status_code=200)
resp = app.get('/', status=200)
resp.form['value'] = 'plop'
resp.form.fields['items[]'][0].checked = True
with mock.patch('combo.utils.requests.request') as requests_post:
requests_post.return_value = mock.Mock(content=json.dumps({'err': 0}), status_code=200)
resp2 = resp.form.submit()
assert requests_post.call_args[0][0] == 'POST'
assert requests_post.call_args[0][1] == 'http://test-post-cell/create/'
assert requests_post.call_args[1]['json'] == {'value': 'plop', 'items': ['1']}
assert urlparse(resp2.location).path == '/'
# check ajax call
with mock.patch('combo.utils.requests.request') as requests_post:
requests_post.return_value = mock.Mock(content=json.dumps({'err': 0}), status_code=200)
resp2 = resp.form.submit(headers={'x-requested-with': 'XMLHttpRequest'})
assert requests_post.call_args[0][0] == 'POST'
assert requests_post.call_args[0][1] == 'http://test-post-cell/create/'
assert requests_post.call_args[1]['json'] == {'value': 'plop', 'items': ['1']}
assert resp2.content.startswith('<form')
# check error on POST
with mock.patch('combo.utils.requests.request') as requests_post:
requests_post.return_value = mock.Mock(content=json.dumps({'err': 0}), status_code=400)
resp2 = resp.form.submit()
assert urlparse(resp2.location).path == '/'
resp2 = resp2.follow()
assert 'Error sending data.' in resp2.content
settings.JSON_CELL_TYPES['test-post-cell']['actions']['create']['error-message'] = 'Failed to create stuff.'
resp2 = resp.form.submit()
assert urlparse(resp2.location).path == '/'
resp2 = resp2.follow()
assert 'Failed to create stuff.' in resp2.content
with mock.patch('combo.utils.requests.request') as requests_post:
requests_post.return_value = mock.Mock(content=json.dumps({'err': 0}), status_code=400)
resp2 = resp.form.submit(headers={'x-requested-with': 'XMLHttpRequest'})
assert resp2.content.startswith('<form')
assert resp2.headers['x-error-message'] == 'Failed to create stuff.'
# check variable substitution in URL
with mock.patch('combo.utils.requests.request') as requests_post:
settings.JSON_CELL_TYPES['test-post-cell']['actions']['create'] = {
'url': 'http://test-post-cell/[value]/delete'}
resp2 = resp.form.submit()
assert requests_post.call_args[0][0] == 'POST'
assert requests_post.call_args[0][1] == 'http://test-post-cell/plop/delete'
# check patch method
with mock.patch('combo.utils.requests.request') as requests_patch:
resp.form['action'] = 'update'
requests_patch.return_value = mock.Mock(content=json.dumps({'err': 0}), status_code=200)
resp2 = resp.form.submit()
assert requests_patch.call_args[0][0] == 'PATCH'
assert requests_patch.call_args[0][1] == 'http://test-post-cell/update/'
assert requests_patch.call_args[1]['json'] == {'value': 'plop', 'items': ['1']}
assert urlparse(resp2.location).path == '/'
def test_familyinfos_cell_with_placeholders(app, admin_user):
Page.objects.all().delete()
page = Page(title='Family', slug='index', template_name='standard')
page.save()
family_cell = FamilyInfosCell(page=page, placeholder='content', order=0)
family_cell.save()
TextCell(page=page, placeholder='family_unlinked_user', text='<p>Hello anonymous user</p>', order=0,
restricted_to_unlogged=True, public=True).save()
TextCell(page=page, placeholder='family_unlinked_user', text='<p>You are not linked</p>',
order=1, public=False, restricted_to_unlogged=False).save()
with override_settings(FAMILY_SERVICE={'root': '/family/'}):
with mock.patch('combo.utils.requests.send') as requests_send:
resp = app.get('/')
assert "<p>Hello anonymous user</p>" in resp.content
assert "<p>You are not linked</p>" not in resp.content
app = login(app)
data = {'err': 0, 'data': None}
requests_send.return_value = mock.Mock(json=lambda: data, content=json.dumps(data), status_code=200)
# make sure no data are loaded
resp = app.get('/')
resp.html.body.find('div', {'class': 'familyinfoscell'}).text.strip() == 'Loading...'
# put data in cache
resp = app.get(reverse('combo-public-ajax-page-cell',
kwargs={'page_pk': page.pk, 'cell_reference': family_cell.get_reference()}))
resp = app.get('/')
assert "<p>Hello anonymous user</p>" not in resp.content
assert "<p>You are not linked</p>" in resp.content
@mock.patch('combo.utils.requests.get')
def test_familycell_link(requests_get, app, settings, admin_user):
settings.FAMILY_SERVICE = {'root': '/family/'}
Page.objects.all().delete()
page = Page(title='Family', slug='index', template_name='standard')
page.save()
family_cell = FamilyInfosCell(page=page, placeholder='content', order=0)
family_cell.save()
TextCell(page=page, placeholder='family_unlinked_user', text='<p>Hello anonymous user</p>', order=0,
restricted_to_unlogged=True, public=True).save()
TextCell(page=page, placeholder='family_unlinked_user', text='<p>You are not linked</p>',
order=1, public=False, restricted_to_unlogged=False).save()
app = login(app)
# when linking fails
requests_get.return_value = mock.Mock(ok=False, json=lambda: {'err': 102})
resp = app.get('/family/link/')
resp.form['family_id'] = '123'
resp.form['family_code'] = 'l33t'
resp = resp.form.submit().follow()
messages = resp.context['messages']
assert len(messages._loaded_messages) == 1
assert messages._loaded_messages[0].message == 'Failed to link to family. Please check your credentials and retry later.'
requests_get.return_value = mock.Mock(ok=True, json=lambda: {})
resp.form['family_id'] = '123'
resp.form['family_code'] = 'l33t'
resp = resp.form.submit().follow()
messages = resp.context['messages']
assert len(messages._loaded_messages) == 2
assert messages._loaded_messages[1].message == 'Your account was successfully linked.'
@mock.patch('combo.utils.requests.get')
def test_familycell_unlink(requests_get, app, settings, admin_user):
settings.FAMILY_SERVICE = {'root': '/family/'}
Page.objects.all().delete()
page = Page(title='Family', slug='index', template_name='standard')
page.save()
family_cell = FamilyInfosCell(page=page, placeholder='content', order=0)
family_cell.save()
TextCell(page=page, placeholder='family_unlinked_user', text='<p>Hello anonymous user</p>', order=0,
restricted_to_unlogged=True, public=True).save()
TextCell(page=page, placeholder='family_unlinked_user', text='<p>You are not linked</p>',
order=1, public=False, restricted_to_unlogged=False).save()
app = login(app)
requests_get.return_value = mock.Mock(ok=False, json=lambda: {'err': 102})
resp = app.get('/family/unlink/')
resp = resp.form.submit().follow()
messages = resp.context['messages']
assert len(messages._loaded_messages) == 1
assert messages._loaded_messages[0].message == 'An error occured when unlinking.'
requests_get.return_value = mock.Mock(ok=True, json=lambda: {})
resp = app.get('/family/unlink/')
resp = resp.form.submit().follow()
messages = resp.context['messages']
assert len(messages._loaded_messages) == 2
assert messages._loaded_messages[1].message == 'Your account was successfully unlinked.'
def test_synchronous_placeholder(app):
page = Page(title=u'foo', slug='foo', template_name='standard', order=0)
page.save()
cell = FeedCell(page=page, placeholder='content', url='http://example.net', order=1)
cell.save()
templates_settings = [settings.TEMPLATES[0].copy()]
templates_settings[0]['DIRS'] = ['%s/templates-1' % os.path.abspath(os.path.dirname(__file__))]
with override_settings(COMBO_PUBLIC_TEMPLATES={
'standard': {
'name': 'Test',
'template': 'combo/page_template_synchronous_placeholder.html',
}},
TEMPLATES=templates_settings
):
with mock.patch('requests.get') as requests_get:
requests_get.return_value = mock.Mock(content='', status_code=200)
resp = app.get('/foo/', status=200)
assert not 'data-ajax-cell-must-load="true"' in resp.body
cell = FeedCell(page=page, placeholder='sidebar', url='http://example.org', order=1)
cell.save()
resp = app.get('/foo/', status=200)
assert resp.body.count('data-ajax-cell-must-load="true"') == 1
def test_redirects(app):
Redirect.objects.all().delete()
Page.objects.all().delete()
page = Page(title='Home', slug='index', template_name='standard')
page.save()
page2 = Page(title='Second', slug='second', template_name='standard')
page2.save()
page3 = Page(title='Third', slug='third', template_name='standard', parent=page2)
page3.save()
app.get('/whatever/', status=404)
redirect = Redirect(old_url='/whatever/', page=page3)
redirect.save()
assert urlparse(app.get('/whatever/', status=301).location).path == '/second/third/'
assert urlparse(app.get('/whatever', status=301).location).path == '/whatever/'
# check the most recent redirect is called
redirect = Redirect(old_url='/whatever/', page=page2)
redirect.save()
assert urlparse(app.get('/whatever/', status=301).location).path == '/second/'
# rename page
page3.slug = 'third2'
page3.save()
assert app.get('/second/third2/', status=200)
assert urlparse(app.get('/second/third/', status=301).location).path == '/second/third2/'
page2.slug = 'second2'
page2.save()
assert urlparse(app.get('/second/third/', status=301).location).path == '/second2/third2/'
assert urlparse(app.get('/second/third2/', status=301).location).path == '/second2/third2/'
assert urlparse(app.get('/second/', status=301).location).path == '/second2/'
# change parent
page3.parent = None
page3.save()
assert urlparse(app.get('/second/third/', status=301).location).path == '/third2/'
assert urlparse(app.get('/second2/third2/', status=301).location).path == '/third2/'