Skip to content

Commit 073748c

Browse files
committed
Improve testing of django-csp integration
Some of these changes are stylistic, such as renaming _get_ns to get_namespaces. Important changes: - Adds tests for specific panels that use scripts - Fixes redirects panel to actually use the nonce - Fetches the toolbar instance from the store rather than context
1 parent 260d2fb commit 073748c

File tree

4 files changed

+79
-29
lines changed

4 files changed

+79
-29
lines changed

debug_toolbar/panels/redirects.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ def process_request(self, request):
2121
if redirect_to:
2222
status_line = f"{response.status_code} {response.reason_phrase}"
2323
cookies = response.cookies
24-
context = {"redirect_to": redirect_to, "status_line": status_line}
24+
context = {
25+
"redirect_to": redirect_to,
26+
"status_line": status_line,
27+
"toolbar": self.toolbar,
28+
}
2529
# Using SimpleTemplateResponse avoids running global context processors.
2630
response = SimpleTemplateResponse(
2731
"debug_toolbar/redirect.html", context

debug_toolbar/toolbar.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import re
66
import uuid
77
from functools import lru_cache
8+
9+
# Can be removed when python3.8 is dropped
810
from typing import OrderedDict
911

1012
from django.apps import apps

requirements_dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ html5lib
1111
selenium
1212
tox
1313
black
14+
django-csp # Used in tests/test_csp_rendering
1415

1516
# Integration support
1617

tests/test_csp_rendering.py

Lines changed: 71 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22
from xml.etree.ElementTree import Element
33

44
from django.conf import settings
5-
from django.http.response import HttpResponse
65
from django.test.utils import ContextList, override_settings
76
from html5lib.constants import E
87
from html5lib.html5parser import HTMLParser
98

9+
from debug_toolbar.toolbar import DebugToolbar
10+
1011
from .base import BaseTestCase
1112

1213

13-
def _get_ns(element: Element) -> Dict[str, str]:
14+
def get_namespaces(element: Element) -> Dict[str, str]:
1415
"""
1516
Return the default `xmlns`. See
1617
https://docs.python.org/3/library/xml.etree.elementtree.html#parsing-xml-with-namespaces
@@ -20,10 +21,12 @@ def _get_ns(element: Element) -> Dict[str, str]:
2021
return {"": element.tag[1:].split("}", maxsplit=1)[0]}
2122

2223

24+
@override_settings(DEBUG=True)
2325
class CspRenderingTestCase(BaseTestCase):
24-
"Testing if `csp-nonce` renders."
26+
"""Testing if `csp-nonce` renders."""
2527

26-
panel_id = "StaticFilesPanel"
28+
def setUp(self):
29+
self.parser = HTMLParser()
2730

2831
def _fail_if_missing(
2932
self, root: Element, path: str, namespaces: Dict[str, str], nonce: str
@@ -46,50 +49,90 @@ def _fail_if_found(self, root: Element, path: str, namespaces: Dict[str, str]):
4649
raise self.failureException(f"{item} has no nonce attribute.")
4750

4851
def _fail_on_invalid_html(self, content: bytes, parser: HTMLParser):
49-
"Fail if the passed HTML is invalid."
52+
"""Fail if the passed HTML is invalid."""
5053
if parser.errors:
5154
default_msg = ["Content is invalid HTML:"]
5255
lines = content.split(b"\n")
53-
for position, errorcode, datavars in parser.errors:
54-
default_msg.append(" %s" % E[errorcode] % datavars)
56+
for position, error_code, data_vars in parser.errors:
57+
default_msg.append(" %s" % E[error_code] % data_vars)
5558
default_msg.append(" %r" % lines[position[0] - 1])
5659
msg = self._formatMessage(None, "\n".join(default_msg))
5760
raise self.failureException(msg)
5861

5962
@override_settings(
60-
DEBUG=True, MIDDLEWARE=settings.MIDDLEWARE + ["csp.middleware.CSPMiddleware"]
63+
MIDDLEWARE=settings.MIDDLEWARE + ["csp.middleware.CSPMiddleware"]
6164
)
6265
def test_exists(self):
63-
"A `nonce` should exists when using the `CSPMiddleware`."
66+
"""A `nonce` should exist when using the `CSPMiddleware`."""
6467
response = self.client.get(path="/regular/basic/")
65-
if not isinstance(response, HttpResponse):
66-
raise self.failureException(f"{response!r} is not a HttpResponse")
6768
self.assertEqual(response.status_code, 200)
68-
parser = HTMLParser()
69-
el_htmlroot: Element = parser.parse(stream=response.content)
70-
self._fail_on_invalid_html(content=response.content, parser=parser)
69+
70+
html_root: Element = self.parser.parse(stream=response.content)
71+
self._fail_on_invalid_html(content=response.content, parser=self.parser)
7172
self.assertContains(response, "djDebug")
72-
namespaces = _get_ns(element=el_htmlroot)
73-
context: ContextList = response.context # pyright: ignore[reportAttributeAccessIssue]
73+
74+
namespaces = get_namespaces(element=html_root)
75+
toolbar = list(DebugToolbar._store.values())[0]
76+
nonce = str(toolbar.request.csp_nonce)
77+
self._fail_if_missing(
78+
root=html_root, path=".//link", namespaces=namespaces, nonce=nonce
79+
)
80+
self._fail_if_missing(
81+
root=html_root, path=".//script", namespaces=namespaces, nonce=nonce
82+
)
83+
84+
@override_settings(
85+
DEBUG_TOOLBAR_CONFIG={"DISABLE_PANELS": set()},
86+
MIDDLEWARE=settings.MIDDLEWARE + ["csp.middleware.CSPMiddleware"],
87+
)
88+
def test_redirects_exists(self):
89+
response = self.client.get("/redirect/")
90+
self.assertEqual(response.status_code, 200)
91+
92+
html_root: Element = self.parser.parse(stream=response.content)
93+
self._fail_on_invalid_html(content=response.content, parser=self.parser)
94+
self.assertContains(response, "djDebug")
95+
96+
namespaces = get_namespaces(element=html_root)
97+
context: ContextList = response.context
7498
nonce = str(context["toolbar"].request.csp_nonce)
7599
self._fail_if_missing(
76-
root=el_htmlroot, path=".//link", namespaces=namespaces, nonce=nonce
100+
root=html_root, path=".//link", namespaces=namespaces, nonce=nonce
77101
)
78102
self._fail_if_missing(
79-
root=el_htmlroot, path=".//script", namespaces=namespaces, nonce=nonce
103+
root=html_root, path=".//script", namespaces=namespaces, nonce=nonce
80104
)
81105

82-
@override_settings(DEBUG=True)
106+
@override_settings(
107+
MIDDLEWARE=settings.MIDDLEWARE + ["csp.middleware.CSPMiddleware"]
108+
)
109+
def test_panel_content_nonce_exists(self):
110+
response = self.client.get("/regular/basic/")
111+
self.assertEqual(response.status_code, 200)
112+
113+
toolbar = list(DebugToolbar._store.values())[0]
114+
panels_to_check = ["HistoryPanel", "TimerPanel"]
115+
for panel in panels_to_check:
116+
content = toolbar.get_panel_by_id(panel).content
117+
html_root: Element = self.parser.parse(stream=content)
118+
namespaces = get_namespaces(element=html_root)
119+
nonce = str(toolbar.request.csp_nonce)
120+
self._fail_if_missing(
121+
root=html_root, path=".//link", namespaces=namespaces, nonce=nonce
122+
)
123+
self._fail_if_missing(
124+
root=html_root, path=".//script", namespaces=namespaces, nonce=nonce
125+
)
126+
83127
def test_missing(self):
84-
"A `nonce` should not exist when not using the `CSPMiddleware`."
128+
"""A `nonce` should not exist when not using the `CSPMiddleware`."""
85129
response = self.client.get(path="/regular/basic/")
86-
if not isinstance(response, HttpResponse):
87-
raise self.failureException(f"{response!r} is not a HttpResponse")
88130
self.assertEqual(response.status_code, 200)
89-
parser = HTMLParser()
90-
el_htmlroot: Element = parser.parse(stream=response.content)
91-
self._fail_on_invalid_html(content=response.content, parser=parser)
131+
132+
html_root: Element = self.parser.parse(stream=response.content)
133+
self._fail_on_invalid_html(content=response.content, parser=self.parser)
92134
self.assertContains(response, "djDebug")
93-
namespaces = _get_ns(element=el_htmlroot)
94-
self._fail_if_found(root=el_htmlroot, path=".//link", namespaces=namespaces)
95-
self._fail_if_found(root=el_htmlroot, path=".//script", namespaces=namespaces)
135+
136+
namespaces = get_namespaces(element=html_root)
137+
self._fail_if_found(root=html_root, path=".//link", namespaces=namespaces)
138+
self._fail_if_found(root=html_root, path=".//script", namespaces=namespaces)

0 commit comments

Comments
 (0)