mirror of
https://github.com/zulip/zulip.git
synced 2025-11-06 15:03:34 +00:00
test_templates: Remove shallow template rendering code.
This code was very useful when first implemented to help catch errors where our backend templates didn't render, but has been superceded by the success of our URL coverage testing (which ensures every URL supported by Zulip's urls.py is accessed by our tests, with a few exceptions) and other tests covering all of the emails Zulip sends. It has a significant maintenance cost because it's a bit hacky and involves generating fake context, so it makes sense to remove these. Any future coverage issues with templates should be addressed with a direct test that just accessing the relevant URL or sends the relevant email.
This commit is contained in:
@@ -7,7 +7,6 @@ from typing import (
|
|||||||
from django.urls.resolvers import LocaleRegexURLResolver
|
from django.urls.resolvers import LocaleRegexURLResolver
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
from django.template import loader
|
|
||||||
from django.http import HttpResponse, HttpResponseRedirect
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
from django.db.migrations.state import StateApps
|
from django.db.migrations.state import StateApps
|
||||||
from boto.s3.connection import S3Connection
|
from boto.s3.connection import S3Connection
|
||||||
@@ -443,36 +442,6 @@ def write_instrumentation_reports(full_suite: bool, include_webhooks: bool) -> N
|
|||||||
print(" %s" % (untested_pattern,))
|
print(" %s" % (untested_pattern,))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def get_all_templates() -> List[str]:
|
|
||||||
templates = []
|
|
||||||
|
|
||||||
relpath = os.path.relpath
|
|
||||||
isfile = os.path.isfile
|
|
||||||
path_exists = os.path.exists
|
|
||||||
|
|
||||||
def is_valid_template(p: str, n: str) -> bool:
|
|
||||||
return 'webhooks' not in p \
|
|
||||||
and not n.startswith('.') \
|
|
||||||
and not n.startswith('__init__') \
|
|
||||||
and not n.endswith('.md') \
|
|
||||||
and not n.endswith('.source.html') \
|
|
||||||
and isfile(p)
|
|
||||||
|
|
||||||
def process(template_dir: str, dirname: str, fnames: Iterable[str]) -> None:
|
|
||||||
for name in fnames:
|
|
||||||
path = os.path.join(dirname, name)
|
|
||||||
if is_valid_template(path, name):
|
|
||||||
templates.append(relpath(path, template_dir))
|
|
||||||
|
|
||||||
for engine in loader.engines.all():
|
|
||||||
template_dirs = [d for d in engine.template_dirs if path_exists(d)]
|
|
||||||
for template_dir in template_dirs:
|
|
||||||
template_dir = os.path.normpath(template_dir)
|
|
||||||
for dirpath, dirnames, fnames in os.walk(template_dir):
|
|
||||||
process(template_dir, dirpath, fnames)
|
|
||||||
|
|
||||||
return templates
|
|
||||||
|
|
||||||
def load_subdomain_token(response: HttpResponse) -> Dict[str, Any]:
|
def load_subdomain_token(response: HttpResponse) -> Dict[str, Any]:
|
||||||
assert isinstance(response, HttpResponseRedirect)
|
assert isinstance(response, HttpResponseRedirect)
|
||||||
token = response.url.rsplit('/', 1)[1]
|
token = response.url.rsplit('/', 1)[1]
|
||||||
|
|||||||
@@ -1,217 +1,14 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import mock
|
|
||||||
import re
|
|
||||||
from typing import Any, Dict, Iterable
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from django.contrib.auth.models import AnonymousUser
|
|
||||||
from django.test import override_settings
|
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.test.client import RequestFactory
|
|
||||||
|
|
||||||
from jinja2.exceptions import UndefinedError
|
|
||||||
|
|
||||||
from zerver.context_processors import login_context
|
|
||||||
from zerver.lib.exceptions import InvalidMarkdownIncludeStatement
|
from zerver.lib.exceptions import InvalidMarkdownIncludeStatement
|
||||||
from zerver.lib.test_helpers import get_all_templates
|
|
||||||
from zerver.lib.test_classes import (
|
from zerver.lib.test_classes import (
|
||||||
ZulipTestCase,
|
ZulipTestCase,
|
||||||
)
|
)
|
||||||
from zerver.lib.test_runner import slow
|
|
||||||
from zerver.models import Realm
|
|
||||||
|
|
||||||
|
|
||||||
class get_form_value:
|
|
||||||
def __init__(self, value: Any) -> None:
|
|
||||||
self._value = value
|
|
||||||
|
|
||||||
def value(self) -> Any:
|
|
||||||
return self._value
|
|
||||||
|
|
||||||
|
|
||||||
class DummyForm(Dict[str, Any]):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateTestCase(ZulipTestCase):
|
class TemplateTestCase(ZulipTestCase):
|
||||||
"""
|
|
||||||
Tests that backend template rendering doesn't crash.
|
|
||||||
|
|
||||||
This renders all the Zulip backend templates, passing dummy data
|
|
||||||
as the context, which allows us to verify whether any of the
|
|
||||||
templates are broken enough to not render at all (no verification
|
|
||||||
is done that the output looks right). Please see `get_context`
|
|
||||||
function documentation for more information.
|
|
||||||
"""
|
|
||||||
@slow("Tests a large number of different templates")
|
|
||||||
@override_settings(TERMS_OF_SERVICE=None)
|
|
||||||
def test_templates(self) -> None:
|
|
||||||
|
|
||||||
# Just add the templates whose context has a conflict with other
|
|
||||||
# templates' context in `defer`.
|
|
||||||
defer = ['analytics/activity.html']
|
|
||||||
|
|
||||||
# Django doesn't send template_rendered signal for parent templates
|
|
||||||
# https://code.djangoproject.com/ticket/24622
|
|
||||||
covered = [
|
|
||||||
'zerver/portico.html',
|
|
||||||
'zerver/portico_signup.html',
|
|
||||||
]
|
|
||||||
|
|
||||||
logged_out = [
|
|
||||||
'zerver/compare.html',
|
|
||||||
'zerver/footer.html',
|
|
||||||
]
|
|
||||||
|
|
||||||
logged_in = [
|
|
||||||
'analytics/stats.html',
|
|
||||||
'zerver/landing_nav.html',
|
|
||||||
]
|
|
||||||
unusual = [
|
|
||||||
'zerver/emails/confirm_new_email.subject.txt',
|
|
||||||
'zerver/emails/compiled/confirm_new_email.html',
|
|
||||||
'zerver/emails/confirm_new_email.txt',
|
|
||||||
'zerver/emails/notify_change_in_email.subject.txt',
|
|
||||||
'zerver/emails/compiled/notify_change_in_email.html',
|
|
||||||
'zerver/emails/digest.subject.txt',
|
|
||||||
'zerver/emails/digest.txt',
|
|
||||||
'zerver/emails/followup_day1.subject.txt',
|
|
||||||
'zerver/emails/compiled/followup_day1.html',
|
|
||||||
'zerver/emails/followup_day1.txt',
|
|
||||||
'zerver/emails/followup_day2.subject.txt',
|
|
||||||
'zerver/emails/followup_day2.txt',
|
|
||||||
'zerver/emails/compiled/followup_day2.html',
|
|
||||||
'zerver/emails/compiled/password_reset.html',
|
|
||||||
'corporate/zephyr.html',
|
|
||||||
'corporate/zephyr-mirror.html',
|
|
||||||
'zilencer/enterprise_tos_accept_body.txt',
|
|
||||||
'zerver/zulipchat_migration_tos.html',
|
|
||||||
'zilencer/enterprise_tos_accept_body.txt',
|
|
||||||
'zerver/invalid_email.html',
|
|
||||||
'zerver/invalid_realm.html',
|
|
||||||
'zerver/debug.html',
|
|
||||||
'zerver/base.html',
|
|
||||||
'zerver/portico-header.html',
|
|
||||||
'two_factor/_wizard_forms.html',
|
|
||||||
]
|
|
||||||
|
|
||||||
integrations_regexp = re.compile(r'^zerver/integrations/.*\.html$')
|
|
||||||
|
|
||||||
# Since static/generated/bots/ is searched by Jinja2 for templates,
|
|
||||||
# it mistakes logo files under that directory for templates.
|
|
||||||
bot_logos_regexp = re.compile(r'\w+\/logo\.(svg|png)$')
|
|
||||||
|
|
||||||
skip = covered + defer + logged_out + logged_in + unusual + ['tests/test_markdown.html',
|
|
||||||
'zerver/terms.html',
|
|
||||||
'zerver/privacy.html']
|
|
||||||
|
|
||||||
all_templates = get_all_templates()
|
|
||||||
self.assertEqual(set(skip) - set(all_templates), set())
|
|
||||||
templates = [t for t in all_templates if not (
|
|
||||||
t in skip or integrations_regexp.match(t) or bot_logos_regexp.match(t))]
|
|
||||||
self.render_templates(templates, self.get_context())
|
|
||||||
|
|
||||||
# Test the deferred templates with updated context.
|
|
||||||
update = {'data': [('one', 'two')]}
|
|
||||||
self.render_templates(defer, self.get_context(**update))
|
|
||||||
|
|
||||||
def render_templates(self, templates: Iterable[str], context: Dict[str, Any]) -> None:
|
|
||||||
for template_name in templates:
|
|
||||||
template = get_template(template_name)
|
|
||||||
try:
|
|
||||||
template.render(context)
|
|
||||||
except UndefinedError as e: # nocoverage # ideally, this block shouldn't have to execute
|
|
||||||
raise UndefinedError(e.message + """\n
|
|
||||||
This test is designed to confirm that every Jinja2 template is free
|
|
||||||
of syntax errors. There are two common causes for this test failing:
|
|
||||||
|
|
||||||
* One of Zulip's HTML templates doesn't render.
|
|
||||||
* A new context variable was added to a template, without a sample
|
|
||||||
value being added to `get_context` in zerver/tests/test_templates.py.
|
|
||||||
|
|
||||||
""")
|
|
||||||
except Exception: # nocoverage # nicer error handler
|
|
||||||
logging.error("Exception while rendering '{}'".format(template.template.name))
|
|
||||||
raise
|
|
||||||
|
|
||||||
def get_context(self, **kwargs: Any) -> Dict[str, Any]:
|
|
||||||
"""Get the dummy context for shallow testing.
|
|
||||||
|
|
||||||
The context returned will always contain a parameter called
|
|
||||||
`shallow_tested`, which tells the signal receiver that the
|
|
||||||
test was not rendered in an actual logical test (so we can
|
|
||||||
still do coverage reporting on which templates have a logical
|
|
||||||
test).
|
|
||||||
|
|
||||||
Note: `context` just holds dummy values used to make the test
|
|
||||||
pass. This context only ensures that the templates do not
|
|
||||||
throw a 500 error when rendered using dummy data. If new
|
|
||||||
required parameters are added to a template, this test will
|
|
||||||
fail; the usual fix is to just update the context below to add
|
|
||||||
the new parameter to the dummy data.
|
|
||||||
|
|
||||||
:param kwargs: Keyword arguments can be used to update the base
|
|
||||||
context.
|
|
||||||
|
|
||||||
"""
|
|
||||||
user_profile = self.example_user('hamlet')
|
|
||||||
realm = user_profile.realm
|
|
||||||
email = user_profile.email
|
|
||||||
|
|
||||||
context = dict(
|
|
||||||
sidebar_index="zerver/help/include/sidebar_index.md",
|
|
||||||
doc_root="/help/",
|
|
||||||
article="zerver/help/index.md",
|
|
||||||
shallow_tested=True,
|
|
||||||
user_profile=user_profile,
|
|
||||||
user=user_profile,
|
|
||||||
form=DummyForm(
|
|
||||||
full_name=get_form_value('John Doe'),
|
|
||||||
terms=get_form_value(True),
|
|
||||||
email=get_form_value(email),
|
|
||||||
emails=get_form_value(email),
|
|
||||||
subdomain=get_form_value("zulip"),
|
|
||||||
next_param=get_form_value("billing")
|
|
||||||
),
|
|
||||||
current_url=lambda: 'www.zulip.com',
|
|
||||||
integrations_dict={},
|
|
||||||
referrer=dict(
|
|
||||||
full_name='John Doe',
|
|
||||||
realm=dict(name='zulip.com'),
|
|
||||||
),
|
|
||||||
message_count=0,
|
|
||||||
messages=[dict(header='Header')],
|
|
||||||
new_streams=dict(html=''),
|
|
||||||
data=dict(title='Title'),
|
|
||||||
device_info={"device_browser": "Chrome",
|
|
||||||
"device_os": "Windows",
|
|
||||||
"device_ip": "127.0.0.1",
|
|
||||||
"login_time": "9:33am NewYork, NewYork",
|
|
||||||
},
|
|
||||||
api_uri_context={},
|
|
||||||
realm_plan_type=Realm.LIMITED,
|
|
||||||
cloud_annual_price=80,
|
|
||||||
seat_count=8,
|
|
||||||
request=RequestFactory().get("/"),
|
|
||||||
invite_as={"MEMBER": 1},
|
|
||||||
max_file_upload_size = 25,
|
|
||||||
avatar_urls={"john@gmail.com": "www.zulip.com"},
|
|
||||||
realm_admin_emails=lambda _: "admin emails",
|
|
||||||
get_discount_for_realm=lambda _: 0,
|
|
||||||
realm_icon_url=lambda _: "url",
|
|
||||||
realm=realm,
|
|
||||||
)
|
|
||||||
|
|
||||||
# A necessary block to make login_context available to templates.
|
|
||||||
request = mock.MagicMock()
|
|
||||||
request.user = AnonymousUser()
|
|
||||||
request.realm = realm
|
|
||||||
context.update(login_context(request))
|
|
||||||
|
|
||||||
context.update(kwargs)
|
|
||||||
return context
|
|
||||||
|
|
||||||
def test_markdown_in_template(self) -> None:
|
def test_markdown_in_template(self) -> None:
|
||||||
template = get_template("tests/test_markdown.html")
|
template = get_template("tests/test_markdown.html")
|
||||||
context = {
|
context = {
|
||||||
|
|||||||
Reference in New Issue
Block a user