kopia lustrzana https://github.com/wagtail/wagtail
Add new test assertions to WagtailPageTestCase
- Add assertions, and move them to a new TestCase that allows use without forcing authentication for every test - Add routes and preview modes to RoutablePageTest to facilitate testing - Move assertion tests out of admin app - Add custom test assertions for pages - Use default value for exclude_csrf in assertPageIsEditable - Use publish action when posting in assertPageIsEditable for better coverage - Update assertPageIsEditable to always make both a GET and POST requestpull/9388/head
rodzic
284535166a
commit
f6a92bf7d2
|
@ -1,6 +1,8 @@
|
|||
from django.http import HttpResponse
|
||||
from django.shortcuts import redirect
|
||||
|
||||
from wagtail.contrib.routable_page.models import RoutablePage, path, re_path, route
|
||||
from wagtail.models import PreviewableMixin
|
||||
|
||||
|
||||
def routable_page_external_view(request, arg="ARG NOT SET"):
|
||||
|
@ -29,6 +31,14 @@ class RoutablePageTest(RoutablePage):
|
|||
def archive_by_category(self, request, category_slug):
|
||||
return HttpResponse("ARCHIVE BY CATEGORY: " + category_slug)
|
||||
|
||||
@route(r"^permanant-homepage-redirect/$")
|
||||
def permanent_homepage_redirect(self, request):
|
||||
return redirect("/", permanent=True)
|
||||
|
||||
@route(r"^temporary-homepage-redirect/$")
|
||||
def temporary_homepage_redirect(self, request):
|
||||
return redirect("/", permanent=False)
|
||||
|
||||
@route(r"^external/(.+)/$")
|
||||
@route(r"^external-no-arg/$")
|
||||
def external_view(self, *args, **kwargs):
|
||||
|
@ -59,6 +69,16 @@ class RoutablePageTest(RoutablePage):
|
|||
"not-a-valid-route",
|
||||
]
|
||||
|
||||
preview_modes = PreviewableMixin.DEFAULT_PREVIEW_MODES + [
|
||||
("extra", "Extra"),
|
||||
("broken", "Broken"),
|
||||
]
|
||||
|
||||
def serve_preview(self, request, mode_name):
|
||||
if mode_name == "broken":
|
||||
raise AttributeError("Something is broken!")
|
||||
return super().serve_preview(request, mode_name)
|
||||
|
||||
|
||||
class RoutablePageWithOverriddenIndexRouteTest(RoutablePage):
|
||||
@route(r"^$")
|
||||
|
|
|
@ -5,6 +5,8 @@ page types, it can be difficult to construct this data structure by hand;
|
|||
the ``wagtail.test.utils.form_data`` module provides a set of helper
|
||||
functions to assist with this.
|
||||
"""
|
||||
import bs4
|
||||
from django.http import QueryDict
|
||||
|
||||
from wagtail.admin.rich_text import get_rich_text_editor_widget
|
||||
|
||||
|
@ -137,3 +139,62 @@ def rich_text(value, editor="default", features=None):
|
|||
"""
|
||||
widget = get_rich_text_editor_widget(editor, features)
|
||||
return widget.format_value(value)
|
||||
|
||||
|
||||
def _querydict_from_form(form: bs4.Tag, exclude_csrf: bool = True) -> QueryDict:
|
||||
data = QueryDict(mutable=True)
|
||||
for input in form.find_all("input"):
|
||||
name = input.attrs.get("name")
|
||||
if (
|
||||
name
|
||||
and input.attrs.get("type", "") not in ("checkbox", "radio")
|
||||
and (not exclude_csrf or name != "csrfmiddlewaretoken")
|
||||
):
|
||||
data[name] = input.attrs.get("value", "")
|
||||
|
||||
for input in form.find_all("input", type="radio", checked=True):
|
||||
name = input.attrs.get("name")
|
||||
if name:
|
||||
data[name] = input.attrs.get("value")
|
||||
|
||||
for input in form.find_all("input", type="checkbox", checked=True):
|
||||
name = input.attrs.get("name")
|
||||
if name:
|
||||
data.appendlist(name, input.attrs.get("value", ""))
|
||||
|
||||
for textarea in form.find_all("textarea"):
|
||||
name = textarea.attrs.get("name")
|
||||
if name:
|
||||
data[name] = textarea.get_text()
|
||||
|
||||
for select in form.find_all("select"):
|
||||
name = select.attrs.get("name")
|
||||
if name:
|
||||
selected_value = False
|
||||
for option in select.find_all("option", selected=True):
|
||||
selected_value = True
|
||||
data.appendlist(name, option.attrs.get("value", option.get_text()))
|
||||
if not selected_value:
|
||||
first_option = select.find("option")
|
||||
if first_option:
|
||||
data[name] = first_option.attrs.get(
|
||||
"value", first_option.get_text()
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
def querydict_from_html(
|
||||
html: str, form_id: str = None, form_index: int = 0, exclude_csrf: bool = True
|
||||
) -> QueryDict:
|
||||
soup = bs4.BeautifulSoup(html, "html5lib")
|
||||
if form_id is not None:
|
||||
form = soup.find("form", attrs={"id": form_id})
|
||||
if form is None:
|
||||
raise ValueError(f'No form was found with id "{form_id}".')
|
||||
return _querydict_from_form(form, exclude_csrf)
|
||||
else:
|
||||
index = int(form_index)
|
||||
for i, form in enumerate(soup.find_all("form", limit=index + 1)):
|
||||
if i == index:
|
||||
return _querydict_from_form(form, exclude_csrf)
|
||||
raise ValueError(f"No form was found with index: {form_index}.")
|
||||
|
|
|
@ -1,18 +1,32 @@
|
|||
from typing import Any, Dict, Optional
|
||||
from unittest import mock
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.base_user import AbstractBaseUser
|
||||
from django.http import Http404
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.text import slugify
|
||||
|
||||
from wagtail.coreutils import get_dummy_request
|
||||
from wagtail.models import Page
|
||||
|
||||
from .form_data import querydict_from_html
|
||||
from .wagtail_tests import WagtailTestUtils
|
||||
|
||||
AUTH_BACKEND = settings.AUTHENTICATION_BACKENDS[0]
|
||||
|
||||
class WagtailPageTests(WagtailTestUtils, TestCase):
|
||||
|
||||
class WagtailPageTestCase(WagtailTestUtils, TestCase):
|
||||
"""
|
||||
A set of asserts to help write tests for your own Wagtail site.
|
||||
A set of assertions to help write tests for custom Wagtail page types
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.login()
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.dummy_request = get_dummy_request()
|
||||
|
||||
def _testCanCreateAt(self, parent_model, child_model):
|
||||
return child_model in parent_model.allowed_subpage_models()
|
||||
|
@ -148,3 +162,288 @@ class WagtailPageTests(WagtailTestUtils, TestCase):
|
|||
self.assertEqual(
|
||||
set(child_model.allowed_parent_page_models()), set(parent_models), msg=msg
|
||||
)
|
||||
|
||||
def assertPageIsRoutable(
|
||||
self,
|
||||
page: Page,
|
||||
route_path: Optional[str] = "/",
|
||||
msg: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Asserts that ``page`` can be routed to without raising a ``Http404`` error.
|
||||
|
||||
For page types with multiple routes, you can use ``route_path`` to specify an alternate route to test.
|
||||
"""
|
||||
path = page.get_url(self.dummy_request)
|
||||
if route_path != "/":
|
||||
path = path.rstrip("/") + "/" + route_path.lstrip("/")
|
||||
|
||||
site = page.get_site()
|
||||
if site is None:
|
||||
msg = self._formatMessage(
|
||||
msg,
|
||||
'Failed to route to "%s" for %s "%s". The page does not belong to any sites.'
|
||||
% (type(page).__name__, route_path, page),
|
||||
)
|
||||
raise self.failureException(msg)
|
||||
|
||||
path_components = [component for component in path.split("/") if component]
|
||||
try:
|
||||
page, args, kwargs = site.root_page.localized.specific.route(
|
||||
self.dummy_request, path_components
|
||||
)
|
||||
except Http404:
|
||||
msg = self._formatMessage(
|
||||
msg,
|
||||
'Failed to route to "%(route_path)s" for %(page_type)s "%(page)s". A Http404 was raised for path: "%(full_path)s".'
|
||||
% {
|
||||
"route_path": route_path,
|
||||
"page_type": type(page).__name__,
|
||||
"page": page,
|
||||
"full_path": path,
|
||||
},
|
||||
)
|
||||
raise self.failureException(msg)
|
||||
|
||||
def assertPageIsRenderable(
|
||||
self,
|
||||
page: Page,
|
||||
route_path: Optional[str] = "/",
|
||||
query_data: Optional[Dict[str, Any]] = None,
|
||||
post_data: Optional[Dict[str, Any]] = None,
|
||||
user: Optional[AbstractBaseUser] = None,
|
||||
accept_404: Optional[bool] = False,
|
||||
accept_redirect: Optional[bool] = False,
|
||||
msg: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Asserts that ``page`` can be rendered without raising a fatal error.
|
||||
|
||||
For page types with multiple routes, you can use ``route_path`` to specify an alternate route to test.
|
||||
|
||||
When ``post_data`` is provided, the test makes a ``POST`` request with ``post_data`` in the request body. Otherwise, a ``GET`` request is made.
|
||||
|
||||
When supplied, ``query_data`` is converted to a querystring and added to the request URL (regardless of whether ``post_data`` is provided).
|
||||
|
||||
When ``user`` is provided, the test is conducted with them as the active user.
|
||||
|
||||
By default, the assertion will fail if the request to the page URL results in a 301, 302 or 404 HTTP response. If you are testing a page/route
|
||||
where a 404 response is expected, you can use ``accept_404=True`` to indicate this, and the assertion will pass when encountering a 404. Likewise,
|
||||
if you are testing a page/route where a redirect response is expected, you can use `accept_redirect=True` to indicate this, and the assertion will
|
||||
pass when encountering 301 or 302.
|
||||
"""
|
||||
if user:
|
||||
self.client.force_login(user, AUTH_BACKEND)
|
||||
|
||||
path = page.get_url(self.dummy_request)
|
||||
if route_path != "/":
|
||||
path = path.rstrip("/") + "/" + route_path.lstrip("/")
|
||||
|
||||
post_kwargs = {}
|
||||
if post_data is not None:
|
||||
post_kwargs = {"data": post_data}
|
||||
if query_data:
|
||||
post_kwargs["QUERYSTRING"] = urlencode(query_data, doseq=True)
|
||||
try:
|
||||
if post_data is None:
|
||||
resp = self.client.get(path, data=query_data)
|
||||
else:
|
||||
resp = self.client.post(path, **post_kwargs)
|
||||
except Exception as e:
|
||||
msg = self._formatMessage(
|
||||
msg,
|
||||
'Failed to render route "%(route_path)s" for %(page_type)s "%(page)s":\n%(exc)s'
|
||||
% {
|
||||
"route_path": route_path,
|
||||
"page_type": type(page).__name__,
|
||||
"page": page,
|
||||
"exc": e,
|
||||
},
|
||||
)
|
||||
raise self.failureException(msg)
|
||||
finally:
|
||||
if user:
|
||||
self.client.logout()
|
||||
|
||||
if (
|
||||
resp.status_code == 200
|
||||
or (accept_404 and resp.status_code == 404)
|
||||
or (accept_redirect and resp.status_code in (301, 302))
|
||||
or isinstance(resp, mock.MagicMock)
|
||||
):
|
||||
return
|
||||
|
||||
msg = self._formatMessage(
|
||||
msg,
|
||||
'Failed to render route "%(route_path)s" for %(page_type)s "%(page)s":\nA HTTP %(code)s response was received for path: "%(full_path)s".'
|
||||
% {
|
||||
"route_path": route_path,
|
||||
"page_type": type(page).__name__,
|
||||
"page": page,
|
||||
"code": resp.status_code,
|
||||
"full_path": path,
|
||||
},
|
||||
)
|
||||
raise self.failureException(msg)
|
||||
|
||||
def assertPageIsEditable(
|
||||
self,
|
||||
page: Page,
|
||||
post_data: Optional[Dict[str, Any]] = None,
|
||||
user: Optional[AbstractBaseUser] = None,
|
||||
msg: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Asserts that the page edit view works for ``page`` without raising a fatal error.
|
||||
|
||||
When ``user`` is provided, the test is conducted with them as the active user. Otherwise, a superuser is created and used for the test.
|
||||
|
||||
After a successful ``GET`` request, a ``POST`` request is made with field data in the request body. If ``post_data`` is provided, that will be used for this purpose. If not, this data will be extracted from the ``GET`` response HTML.
|
||||
"""
|
||||
if user:
|
||||
# rule out permission issues early on
|
||||
if not page.permissions_for_user(user).can_edit():
|
||||
self._formatMessage(
|
||||
msg,
|
||||
'Failed to load edit view for %(page_type)s "%(page)s":\nUser "%(user)s" have insufficient permissions.'
|
||||
% {
|
||||
"page_type": type(page).__name__,
|
||||
"page": page,
|
||||
"user": user,
|
||||
},
|
||||
)
|
||||
raise self.failureException(msg)
|
||||
else:
|
||||
if not hasattr(self, "_pageiseditable_superuser"):
|
||||
self._pageiseditable_superuser = self.create_superuser(
|
||||
"assertpageiseditable"
|
||||
)
|
||||
user = self._pageiseditable_superuser
|
||||
|
||||
self.client.force_login(user, AUTH_BACKEND)
|
||||
|
||||
path = reverse("wagtailadmin_pages:edit", kwargs={"page_id": page.id})
|
||||
try:
|
||||
response = self.client.get(path)
|
||||
except Exception as e:
|
||||
self.client.logout()
|
||||
msg = self._formatMessage(
|
||||
msg,
|
||||
'Failed to load edit view via GET for %(page_type)s "%(page)s":\n%(exc)s'
|
||||
% {"page_type": type(page).__name__, "page": page, "exc": e},
|
||||
)
|
||||
raise self.failureException(msg)
|
||||
if response.status_code != 200:
|
||||
self.client.logout()
|
||||
msg = self._formatMessage(
|
||||
msg,
|
||||
'Failed to load edit view via GET for %(page_type)s "%(page)s":\nReceived response with HTTP status code: %(code)s.'
|
||||
% {
|
||||
"page_type": type(page).__name__,
|
||||
"page": page,
|
||||
"code": response.status_code,
|
||||
},
|
||||
)
|
||||
raise self.failureException(msg)
|
||||
|
||||
if post_data is not None:
|
||||
data_to_post = post_data
|
||||
else:
|
||||
data_to_post = querydict_from_html(
|
||||
response.content.decode(), form_id="page-edit-form"
|
||||
)
|
||||
data_to_post["action-publish"] = ""
|
||||
|
||||
try:
|
||||
response = self.client.post(path, data_to_post)
|
||||
except Exception as e:
|
||||
msg = self._formatMessage(
|
||||
msg,
|
||||
'Failed to load edit view via POST for %(page_type)s "%(page)s":\n%(exc)s'
|
||||
% {"page_type": type(page).__name__, "page": page, "exc": e},
|
||||
)
|
||||
raise self.failureException(msg)
|
||||
finally:
|
||||
page.save() # undo any changes to page
|
||||
self.client.logout()
|
||||
|
||||
def assertPageIsPreviewable(
|
||||
self,
|
||||
page: Page,
|
||||
mode: Optional[str] = "",
|
||||
post_data: Optional[Dict[str, Any]] = None,
|
||||
user: Optional[AbstractBaseUser] = None,
|
||||
msg: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Asserts that the page preview view can be loaded for ``page`` without raising a fatal error.
|
||||
|
||||
For page types that support multiple preview modes, ``mode`` can be used to specify the preview mode to be tested.
|
||||
|
||||
When ``user`` is provided, the test is conducted with them as the active user. Otherwise, a superuser is created and used for the test.
|
||||
|
||||
To load the preview, the test client needs to make a ``POST`` request including all required field data in the request body.
|
||||
If ``post_data`` is provided, that will be used for this purpose. If not, the method will attempt to extract this data from the page edit view.
|
||||
"""
|
||||
if not user:
|
||||
if not hasattr(self, "_pageispreviewable_superuser"):
|
||||
self._pageispreviewable_superuser = self.create_superuser(
|
||||
"assertpageispreviewable"
|
||||
)
|
||||
user = self._pageispreviewable_superuser
|
||||
|
||||
self.client.force_login(user, AUTH_BACKEND)
|
||||
|
||||
if post_data is None:
|
||||
edit_path = reverse("wagtailadmin_pages:edit", kwargs={"page_id": page.id})
|
||||
html = self.client.get(edit_path).content.decode()
|
||||
post_data = querydict_from_html(html, form_id="page-edit-form")
|
||||
|
||||
preview_path = reverse(
|
||||
"wagtailadmin_pages:preview_on_edit", kwargs={"page_id": page.id}
|
||||
)
|
||||
try:
|
||||
response = self.client.post(
|
||||
preview_path, data=post_data, QUERYSTRING=f"mode={mode}"
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertJSONEqual(
|
||||
response.content.decode(),
|
||||
{"is_valid": True, "is_available": True},
|
||||
)
|
||||
except Exception as e:
|
||||
self.client.logout()
|
||||
msg = self._formatMessage(
|
||||
msg,
|
||||
'Failed to load preview for %(page_type)s "%(page)s" with mode="%(mode)s":\n%(exc)s'
|
||||
% {
|
||||
"page_type": type(page).__name__,
|
||||
"page": page,
|
||||
"mode": mode,
|
||||
"exc": e,
|
||||
},
|
||||
)
|
||||
raise self.failureException(msg)
|
||||
|
||||
try:
|
||||
self.client.get(preview_path, data={"mode": mode})
|
||||
except Exception as e:
|
||||
msg = self._formatMessage(
|
||||
msg,
|
||||
'Failed to load preview for %(page_type)s "%(page)s" with mode="%(mode)s":\n%(exc)s'
|
||||
% {
|
||||
"page_type": type(page).__name__,
|
||||
"page": page,
|
||||
"mode": mode,
|
||||
"exc": e,
|
||||
},
|
||||
)
|
||||
raise self.failureException(msg)
|
||||
finally:
|
||||
self.client.logout()
|
||||
|
||||
|
||||
class WagtailPageTests(WagtailPageTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.login()
|
||||
|
|
|
@ -0,0 +1,189 @@
|
|||
from django.test import SimpleTestCase
|
||||
|
||||
from wagtail.test.utils.form_data import querydict_from_html
|
||||
|
||||
|
||||
class TestQueryDictFromHTML(SimpleTestCase):
|
||||
html = """
|
||||
<form id="personal-details">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="Z783HTL5Bc2J54WhAtEeR3eefM1FBkq0EbTfNnYnepFGuJSOfvosFvwjeKYtMwFr">
|
||||
<input type="hidden" name="no_value_input">
|
||||
<input type="hidden" value="no name input">
|
||||
<div class="mt-8 max-w-md">
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
<label class="block">
|
||||
<span class="text-gray-700">Full name</span>
|
||||
<input type="text" name="name" value="Jane Doe" class="mt-1 block w-full" placeholder="">
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-gray-700">Email address</span>
|
||||
<input type="email" name="email" class="mt-1 block w-full" value="jane@example.com" placeholder="name@example.com">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<form id="event-details">
|
||||
<div class="mt-8 max-w-md">
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
<label class="block">
|
||||
<span class="text-gray-700">When is your event?</span>
|
||||
<input type="date" name="date" class="mt-1 block w-full" value="2023-01-01">
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-gray-700">What type of event is it?</span>
|
||||
<select name="event_type" class="block w-full mt-1">
|
||||
<option value="corporate">Corporate event</option>
|
||||
<option value="wedding">Wedding</option>
|
||||
<option value="birthday">Birthday</option>
|
||||
<option value="other" selected>Other</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-gray-700">What age groups is it suitable for?</span>
|
||||
<select name="ages" class="block w-full mt-1" multiple>
|
||||
<option>Infants</option>
|
||||
<option>Children</option>
|
||||
<option>Teenagers</option>
|
||||
<option selected>18-30</option>
|
||||
<option selected>30-50</option>
|
||||
<option>50-70</option>
|
||||
<option>70+</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<form id="market-research">
|
||||
<div class="mt-8 max-w-md">
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
<fieldset class="block">
|
||||
<legend>How many pets do you have?</legend>
|
||||
<div class="radio-list">
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="pets" value="0" />
|
||||
None
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="pets" value="1" />
|
||||
One
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="pets" value="2" checked />
|
||||
Two
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="pets" value="3+" />
|
||||
Three or more
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="block">
|
||||
<legend>Which two colours do you like best?</legend>
|
||||
<div class="checkbox-list">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="colours" value="cyan">
|
||||
Cyan
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="colours" value="magenta" checked />
|
||||
Magenta
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="colours" value="yellow" />
|
||||
Yellow
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="colours" value="black" checked />
|
||||
Black
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="colours" value="white" />
|
||||
White
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<label class="block">
|
||||
<span class="text-gray-700">Tell us what you love</span>
|
||||
<textarea name="love" class="mt-1 block w-full" rows="3">Comic books</textarea>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
"""
|
||||
|
||||
personal_details = [
|
||||
("no_value_input", [""]),
|
||||
("name", ["Jane Doe"]),
|
||||
("email", ["jane@example.com"]),
|
||||
]
|
||||
|
||||
event_details = [
|
||||
("date", ["2023-01-01"]),
|
||||
("event_type", ["other"]),
|
||||
("ages", ["18-30", "30-50"]),
|
||||
]
|
||||
|
||||
market_research = [
|
||||
("pets", ["2"]),
|
||||
("colours", ["magenta", "black"]),
|
||||
("love", ["Comic books"]),
|
||||
]
|
||||
|
||||
def test_html_only(self):
|
||||
# data should be extracted from the 'first' form by default
|
||||
result = querydict_from_html(self.html)
|
||||
self.assertEqual(list(result.lists()), self.personal_details)
|
||||
|
||||
def test_include_csrf(self):
|
||||
result = querydict_from_html(self.html, exclude_csrf=False)
|
||||
expected_result = [
|
||||
(
|
||||
"csrfmiddlewaretoken",
|
||||
["Z783HTL5Bc2J54WhAtEeR3eefM1FBkq0EbTfNnYnepFGuJSOfvosFvwjeKYtMwFr"],
|
||||
)
|
||||
] + self.personal_details
|
||||
self.assertEqual(list(result.lists()), expected_result)
|
||||
|
||||
def test_form_index(self):
|
||||
for index, expected_data in (
|
||||
(0, self.personal_details),
|
||||
("2", self.market_research),
|
||||
(1, self.event_details),
|
||||
):
|
||||
result = querydict_from_html(self.html, form_index=index)
|
||||
self.assertEqual(list(result.lists()), expected_data)
|
||||
|
||||
def test_form_id(self):
|
||||
for id, expected_data in (
|
||||
("event-details", self.event_details),
|
||||
("personal-details", self.personal_details),
|
||||
("market-research", self.market_research),
|
||||
):
|
||||
result = querydict_from_html(self.html, form_id=id)
|
||||
self.assertEqual(list(result.lists()), expected_data)
|
||||
|
||||
def test_invalid_form_id(self):
|
||||
with self.assertRaises(ValueError):
|
||||
querydict_from_html(self.html, form_id="invalid-id")
|
||||
|
||||
def test_invalid_index(self):
|
||||
with self.assertRaises(ValueError):
|
||||
querydict_from_html(self.html, form_index=5)
|
|
@ -0,0 +1,173 @@
|
|||
from unittest import mock
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from wagtail.models import Page
|
||||
from wagtail.test.routablepage.models import RoutablePageTest
|
||||
from wagtail.test.utils import WagtailPageTestCase
|
||||
|
||||
|
||||
class TestCustomPageAssertions(WagtailPageTestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.superuser = cls.create_superuser("super")
|
||||
|
||||
def setUp(self):
|
||||
self.parent = Page.objects.get(id=2)
|
||||
self.page = RoutablePageTest(
|
||||
title="Hello world!",
|
||||
slug="hello-world",
|
||||
)
|
||||
self.parent.add_child(instance=self.page)
|
||||
|
||||
def test_is_routable(self):
|
||||
self.assertPageIsRoutable(self.page)
|
||||
|
||||
def test_is_routable_with_alternative_route(self):
|
||||
self.assertPageIsRoutable(self.page, "archive/year/1984/")
|
||||
|
||||
def test_is_routable_fails_for_draft_page(self):
|
||||
self.page.live = False
|
||||
self.page.save()
|
||||
with self.assertRaises(self.failureException):
|
||||
self.assertPageIsRoutable(self.page)
|
||||
|
||||
def test_is_routable_fails_for_invalid_route_path(self):
|
||||
with self.assertRaises(self.failureException):
|
||||
self.assertPageIsRoutable(self.page, "invalid-route-path/")
|
||||
|
||||
@mock.patch("django.test.testcases.Client.get")
|
||||
@mock.patch("django.test.testcases.Client.force_login")
|
||||
def test_is_renderable(self, mocked_force_login, mocked_get):
|
||||
self.assertPageIsRenderable(self.page)
|
||||
mocked_force_login.assert_not_called()
|
||||
mocked_get.assert_called_once_with("/hello-world/", data=None)
|
||||
|
||||
@mock.patch("django.test.testcases.Client.get")
|
||||
@mock.patch("django.test.testcases.Client.force_login")
|
||||
def test_is_renderable_for_alternative_route(self, mocked_force_login, mocked_get):
|
||||
self.assertPageIsRenderable(self.page, "archive/year/1984/")
|
||||
mocked_force_login.assert_not_called()
|
||||
mocked_get.assert_called_once_with("/hello-world/archive/year/1984/", data=None)
|
||||
|
||||
@mock.patch("django.test.testcases.Client.get")
|
||||
@mock.patch("django.test.testcases.Client.force_login")
|
||||
def test_is_renderable_for_user(self, mocked_force_login, mocked_get):
|
||||
self.assertPageIsRenderable(self.page, user=self.superuser)
|
||||
mocked_force_login.assert_called_once_with(
|
||||
self.superuser, settings.AUTHENTICATION_BACKENDS[0]
|
||||
)
|
||||
mocked_get.assert_called_once_with("/hello-world/", data=None)
|
||||
|
||||
@mock.patch("django.test.testcases.Client.get")
|
||||
def test_is_renderable_with_query_data(self, mocked_get):
|
||||
query_data = {"p": 1, "q": "test"}
|
||||
self.assertPageIsRenderable(self.page, query_data=query_data)
|
||||
mocked_get.assert_called_once_with("/hello-world/", data=query_data)
|
||||
|
||||
@mock.patch("django.test.testcases.Client.post")
|
||||
def test_is_renderable_with_query_and_post_data(self, mocked_post):
|
||||
query_data = {"p": 1, "q": "test"}
|
||||
post_data = {"subscribe": True}
|
||||
self.assertPageIsRenderable(
|
||||
self.page, query_data=query_data, post_data=post_data
|
||||
)
|
||||
mocked_post.assert_called_once_with(
|
||||
"/hello-world/", data=post_data, QUERYSTRING="p=1&q=test"
|
||||
)
|
||||
|
||||
def test_is_renderable_for_draft_page(self):
|
||||
self.page.live = False
|
||||
self.page.save()
|
||||
|
||||
# When accept_404 is False (the default) the test should fail
|
||||
with self.assertRaises(self.failureException):
|
||||
self.assertPageIsRenderable(self.page)
|
||||
|
||||
# When accept_404 is True, the test should pass
|
||||
self.assertPageIsRenderable(self.page, accept_404=True)
|
||||
|
||||
def test_is_renderable_for_invalid_route_path(self):
|
||||
# When accept_404 is False (the default) the test should fail
|
||||
with self.assertRaises(self.failureException):
|
||||
self.assertPageIsRenderable(self.page, "invalid-route-path/")
|
||||
|
||||
# When accept_404 is True, the test should pass
|
||||
self.assertPageIsRenderable(self.page, "invalid-route-path/", accept_404=True)
|
||||
|
||||
def test_is_rendereable_accept_redirect(self):
|
||||
redirect_route_paths = [
|
||||
"permanant-homepage-redirect/",
|
||||
"temporary-homepage-redirect/",
|
||||
]
|
||||
|
||||
# When accept_redirect is False (the default) the tests should fail
|
||||
for route_path in redirect_route_paths:
|
||||
with self.assertRaises(self.failureException):
|
||||
self.assertPageIsRenderable(self.page, route_path)
|
||||
|
||||
# When accept_redirect is True, the tests should pass
|
||||
for route_path in redirect_route_paths:
|
||||
self.assertPageIsRenderable(self.page, route_path, accept_redirect=True)
|
||||
|
||||
def test_is_editable(self):
|
||||
self.assertPageIsEditable(self.page)
|
||||
|
||||
@mock.patch("django.test.testcases.Client.force_login")
|
||||
def test_is_editable_always_authenticates(self, mocked_force_login):
|
||||
try:
|
||||
self.assertPageIsEditable(self.page)
|
||||
except self.failureException:
|
||||
pass
|
||||
|
||||
mocked_force_login.assert_called_with(
|
||||
self._pageiseditable_superuser, settings.AUTHENTICATION_BACKENDS[0]
|
||||
)
|
||||
|
||||
try:
|
||||
self.assertPageIsEditable(self.page, user=self.superuser)
|
||||
except self.failureException:
|
||||
pass
|
||||
|
||||
mocked_force_login.assert_called_with(
|
||||
self.superuser, settings.AUTHENTICATION_BACKENDS[0]
|
||||
)
|
||||
|
||||
@mock.patch("django.test.testcases.Client.get")
|
||||
@mock.patch("django.test.testcases.Client.force_login")
|
||||
def test_is_editable_with_permission_lacking_user(
|
||||
self, mocked_force_login, mocked_get
|
||||
):
|
||||
user = self.create_user("bob")
|
||||
with self.assertRaises(self.failureException):
|
||||
self.assertPageIsEditable(self.page, user=user)
|
||||
mocked_force_login.assert_not_called()
|
||||
mocked_get.assert_not_called()
|
||||
|
||||
def test_is_editable_with_post_data(self):
|
||||
self.assertPageIsEditable(
|
||||
self.page,
|
||||
post_data={
|
||||
"title": "Goodbye world?",
|
||||
"slug": "goodbye-world",
|
||||
"content": "goodbye",
|
||||
},
|
||||
)
|
||||
|
||||
def test_is_previewable(self):
|
||||
self.assertPageIsPreviewable(self.page)
|
||||
|
||||
def test_is_previewable_with_post_data(self):
|
||||
self.assertPageIsPreviewable(
|
||||
self.page, post_data={"title": "test", "slug": "test"}
|
||||
)
|
||||
|
||||
def test_is_previewable_with_custom_user(self):
|
||||
self.assertPageIsPreviewable(self.page, user=self.superuser)
|
||||
|
||||
def test_is_previewable_for_alternative_mode(self):
|
||||
self.assertPageIsPreviewable(self.page, mode="extra")
|
||||
|
||||
def test_is_previewable_for_broken_mode(self):
|
||||
with self.assertRaises(self.failureException):
|
||||
self.assertPageIsPreviewable(self.page, mode="broken")
|
Ładowanie…
Reference in New Issue