123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079 |
- import importlib
- import inspect
- import os
- import re
- import sys
- import tempfile
- import threading
- from io import StringIO
- from pathlib import Path
- from unittest import mock, skipIf, skipUnless
- from asgiref.sync import async_to_sync, iscoroutinefunction
- from django.core import mail
- from django.core.files.uploadedfile import SimpleUploadedFile
- from django.db import DatabaseError, connection
- from django.http import Http404, HttpRequest, HttpResponse
- from django.shortcuts import render
- from django.template import TemplateDoesNotExist
- from django.test import RequestFactory, SimpleTestCase, override_settings
- from django.test.utils import LoggingCaptureMixin
- from django.urls import path, reverse
- from django.urls.converters import IntConverter
- from django.utils.functional import SimpleLazyObject
- from django.utils.regex_helper import _lazy_re_compile
- from django.utils.safestring import mark_safe
- from django.utils.version import PY311
- from django.views.debug import (
- CallableSettingWrapper,
- ExceptionCycleWarning,
- ExceptionReporter,
- )
- from django.views.debug import Path as DebugPath
- from django.views.debug import (
- SafeExceptionReporterFilter,
- default_urlconf,
- get_default_exception_reporter_filter,
- technical_404_response,
- technical_500_response,
- )
- from django.views.decorators.debug import sensitive_post_parameters, sensitive_variables
- from ..views import (
- async_sensitive_method_view,
- async_sensitive_method_view_nested,
- async_sensitive_view,
- async_sensitive_view_nested,
- custom_exception_reporter_filter_view,
- index_page,
- multivalue_dict_key_error,
- non_sensitive_view,
- paranoid_view,
- sensitive_args_function_caller,
- sensitive_kwargs_function_caller,
- sensitive_method_view,
- sensitive_view,
- )
- class User:
- def __str__(self):
- return "jacob"
- class WithoutEmptyPathUrls:
- urlpatterns = [path("url/", index_page, name="url")]
- class CallableSettingWrapperTests(SimpleTestCase):
- """Unittests for CallableSettingWrapper"""
- def test_repr(self):
- class WrappedCallable:
- def __repr__(self):
- return "repr from the wrapped callable"
- def __call__(self):
- pass
- actual = repr(CallableSettingWrapper(WrappedCallable()))
- self.assertEqual(actual, "repr from the wrapped callable")
- @override_settings(DEBUG=True, ROOT_URLCONF="view_tests.urls")
- class DebugViewTests(SimpleTestCase):
- def test_files(self):
- with self.assertLogs("django.request", "ERROR"):
- response = self.client.get("/raises/")
- self.assertEqual(response.status_code, 500)
- data = {
- "file_data.txt": SimpleUploadedFile("file_data.txt", b"haha"),
- }
- with self.assertLogs("django.request", "ERROR"):
- response = self.client.post("/raises/", data)
- self.assertContains(response, "file_data.txt", status_code=500)
- self.assertNotContains(response, "haha", status_code=500)
- def test_400(self):
-
- with self.assertLogs("django.security", "WARNING"):
- response = self.client.get("/raises400/")
- self.assertContains(response, '<div class="context" id="', status_code=400)
- def test_400_bad_request(self):
-
- with self.assertLogs("django.request", "WARNING") as cm:
- response = self.client.get("/raises400_bad_request/")
- self.assertContains(response, '<div class="context" id="', status_code=400)
- self.assertEqual(
- cm.records[0].getMessage(),
- "Malformed request syntax: /raises400_bad_request/",
- )
-
- @override_settings(
- TEMPLATES=[
- {
- "BACKEND": "django.template.backends.django.DjangoTemplates",
- }
- ]
- )
- def test_403(self):
- response = self.client.get("/raises403/")
- self.assertContains(response, "<h1>403 Forbidden</h1>", status_code=403)
-
- @override_settings(
- TEMPLATES=[
- {
- "BACKEND": "django.template.backends.django.DjangoTemplates",
- "OPTIONS": {
- "loaders": [
- (
- "django.template.loaders.locmem.Loader",
- {
- "403.html": (
- "This is a test template for a 403 error "
- "({{ exception }})."
- ),
- },
- ),
- ],
- },
- }
- ]
- )
- def test_403_template(self):
- response = self.client.get("/raises403/")
- self.assertContains(response, "test template", status_code=403)
- self.assertContains(response, "(Insufficient Permissions).", status_code=403)
- def test_404(self):
- response = self.client.get("/raises404/")
- self.assertNotContains(
- response,
- '<pre class="exception_value">',
- status_code=404,
- )
- self.assertContains(
- response,
- "<p>The current path, <code>not-in-urls</code>, didn’t match any "
- "of these.</p>",
- status_code=404,
- html=True,
- )
- def test_404_not_in_urls(self):
- response = self.client.get("/not-in-urls")
- self.assertNotContains(response, "Raised by:", status_code=404)
- self.assertNotContains(
- response,
- '<pre class="exception_value">',
- status_code=404,
- )
- self.assertContains(
- response, "Django tried these URL patterns", status_code=404
- )
- self.assertContains(
- response,
- "<code>technical404/ [name='my404']</code>",
- status_code=404,
- html=True,
- )
- self.assertContains(
- response,
- "<p>The current path, <code>not-in-urls</code>, didn’t match any "
- "of these.</p>",
- status_code=404,
- html=True,
- )
-
- self.assertContains(
- response, r"^regex-post/(?P<pk>[0-9]+)/$", status_code=404
- )
- self.assertContains(response, "[name='regex-post']", status_code=404)
-
- self.assertContains(response, r"path-post/<int:pk>/", status_code=404)
- self.assertContains(response, "[name='path-post']", status_code=404)
- @override_settings(ROOT_URLCONF=WithoutEmptyPathUrls)
- def test_404_empty_path_not_in_urls(self):
- response = self.client.get("/")
- self.assertContains(
- response,
- "<p>The empty path didn’t match any of these.</p>",
- status_code=404,
- html=True,
- )
- def test_technical_404(self):
- response = self.client.get("/technical404/")
- self.assertContains(response, '<header id="summary">', status_code=404)
- self.assertContains(response, '<main id="info">', status_code=404)
- self.assertContains(response, '<footer id="explanation">', status_code=404)
- self.assertContains(
- response,
- '<pre class="exception_value">Testing technical 404.</pre>',
- status_code=404,
- html=True,
- )
- self.assertContains(response, "Raised by:", status_code=404)
- self.assertContains(
- response,
- "<td>view_tests.views.technical404</td>",
- status_code=404,
- )
- self.assertContains(
- response,
- "<p>The current path, <code>technical404/</code>, matched the "
- "last one.</p>",
- status_code=404,
- html=True,
- )
- def test_classbased_technical_404(self):
- response = self.client.get("/classbased404/")
- self.assertContains(
- response,
- '<th scope="row">Raised by:</th><td>view_tests.views.Http404View</td>',
- status_code=404,
- html=True,
- )
- def test_technical_500(self):
- with self.assertLogs("django.request", "ERROR"):
- response = self.client.get("/raises500/")
- self.assertContains(response, '<header id="summary">', status_code=500)
- self.assertContains(response, '<main id="info">', status_code=500)
- self.assertContains(response, '<footer id="explanation">', status_code=500)
- self.assertContains(
- response,
- '<th scope="row">Raised during:</th><td>view_tests.views.raises500</td>',
- status_code=500,
- html=True,
- )
- with self.assertLogs("django.request", "ERROR"):
- response = self.client.get("/raises500/", headers={"accept": "text/plain"})
- self.assertContains(
- response,
- "Raised during: view_tests.views.raises500",
- status_code=500,
- )
- def test_classbased_technical_500(self):
- with self.assertLogs("django.request", "ERROR"):
- response = self.client.get("/classbased500/")
- self.assertContains(
- response,
- '<th scope="row">Raised during:</th>'
- "<td>view_tests.views.Raises500View</td>",
- status_code=500,
- html=True,
- )
- with self.assertLogs("django.request", "ERROR"):
- response = self.client.get(
- "/classbased500/", headers={"accept": "text/plain"}
- )
- self.assertContains(
- response,
- "Raised during: view_tests.views.Raises500View",
- status_code=500,
- )
- def test_non_l10ned_numeric_ids(self):
- """
- Numeric IDs and fancy traceback context blocks line numbers shouldn't
- be localized.
- """
- with self.settings(DEBUG=True):
- with self.assertLogs("django.request", "ERROR"):
- response = self.client.get("/raises500/")
-
-
-
- self.assertContains(response, '<div class="context" id="', status_code=500)
- match = re.search(
- b'<div class="context" id="(?P<id>[^"]+)">', response.content
- )
- self.assertIsNotNone(match)
- id_repr = match["id"]
- self.assertFalse(
- re.search(b"[^c0-9]", id_repr),
- "Numeric IDs in debug response HTML page shouldn't be localized "
- "(value: %s)." % id_repr.decode(),
- )
- def test_template_exceptions(self):
- with self.assertLogs("django.request", "ERROR"):
- try:
- self.client.get(reverse("template_exception"))
- except Exception:
- raising_loc = inspect.trace()[-1][-2][0].strip()
- self.assertNotEqual(
- raising_loc.find('raise Exception("boom")'),
- -1,
- "Failed to find 'raise Exception' in last frame of "
- "traceback, instead found: %s" % raising_loc,
- )
- @skipIf(
- sys.platform == "win32",
- "Raises OSError instead of TemplateDoesNotExist on Windows.",
- )
- def test_safestring_in_exception(self):
- with self.assertLogs("django.request", "ERROR"):
- response = self.client.get("/safestring_exception/")
- self.assertNotContains(
- response,
- "<script>alert(1);</script>",
- status_code=500,
- html=True,
- )
- self.assertContains(
- response,
- "<script>alert(1);</script>",
- count=3,
- status_code=500,
- html=True,
- )
- def test_template_loader_postmortem(self):
- """Tests for not existing file"""
- template_name = "notfound.html"
- with tempfile.NamedTemporaryFile(prefix=template_name) as tmpfile:
- tempdir = os.path.dirname(tmpfile.name)
- template_path = os.path.join(tempdir, template_name)
- with (
- override_settings(
- TEMPLATES=[
- {
- "BACKEND": (
- "django.template.backends.django.DjangoTemplates"
- ),
- "DIRS": [tempdir],
- }
- ]
- ),
- self.assertLogs("django.request", "ERROR"),
- ):
- response = self.client.get(
- reverse(
- "raises_template_does_not_exist", kwargs={"path": template_name}
- )
- )
- self.assertContains(
- response,
- "%s (Source does not exist)" % template_path,
- status_code=500,
- count=2,
- )
-
- self.assertContains(
- response,
- "<li><code>django.template.loaders.filesystem.Loader</code>: "
- "%s (Source does not exist)</li>"
- % os.path.join(tempdir, "notfound.html"),
- status_code=500,
- html=True,
- )
- def test_no_template_source_loaders(self):
- """
- Make sure if you don't specify a template, the debug view doesn't blow up.
- """
- with self.assertLogs("django.request", "ERROR"):
- with self.assertRaises(TemplateDoesNotExist):
- self.client.get("/render_no_template/")
- @override_settings(ROOT_URLCONF="view_tests.default_urls")
- def test_default_urlconf_template(self):
- """
- Make sure that the default URLconf template is shown instead of the
- technical 404 page, if the user has not altered their URLconf yet.
- """
- response = self.client.get("/")
- self.assertContains(
- response, "<h1>The install worked successfully! Congratulations!</h1>"
- )
- @override_settings(ROOT_URLCONF="view_tests.regression_21530_urls")
- def test_regression_21530(self):
- """
- Regression test for bug #21530.
- If the admin app include is replaced with exactly one url
- pattern, then the technical 404 template should be displayed.
- The bug here was that an AttributeError caused a 500 response.
- """
- response = self.client.get("/")
- self.assertContains(
- response, "Page not found <small>(404)</small>", status_code=404
- )
- def test_template_encoding(self):
- """
- The templates are loaded directly, not via a template loader, and
- should be opened as utf-8 charset as is the default specified on
- template engines.
- """
- with mock.patch.object(DebugPath, "open") as m:
- default_urlconf(None)
- m.assert_called_once_with(encoding="utf-8")
- m.reset_mock()
- technical_404_response(mock.MagicMock(), mock.Mock())
- m.assert_called_once_with(encoding="utf-8")
- def test_technical_404_converter_raise_404(self):
- with mock.patch.object(IntConverter, "to_python", side_effect=Http404):
- response = self.client.get("/path-post/1/")
- self.assertContains(response, "Page not found", status_code=404)
- def test_exception_reporter_from_request(self):
- with self.assertLogs("django.request", "ERROR"):
- response = self.client.get("/custom_reporter_class_view/")
- self.assertContains(response, "custom traceback text", status_code=500)
- @override_settings(
- DEFAULT_EXCEPTION_REPORTER="view_tests.views.CustomExceptionReporter"
- )
- def test_exception_reporter_from_settings(self):
- with self.assertLogs("django.request", "ERROR"):
- response = self.client.get("/raises500/")
- self.assertContains(response, "custom traceback text", status_code=500)
- @override_settings(
- DEFAULT_EXCEPTION_REPORTER="view_tests.views.TemplateOverrideExceptionReporter"
- )
- def test_template_override_exception_reporter(self):
- with self.assertLogs("django.request", "ERROR"):
- response = self.client.get("/raises500/")
- self.assertContains(
- response,
- "<h1>Oh no, an error occurred!</h1>",
- status_code=500,
- html=True,
- )
- with self.assertLogs("django.request", "ERROR"):
- response = self.client.get("/raises500/", headers={"accept": "text/plain"})
- self.assertContains(response, "Oh dear, an error occurred!", status_code=500)
- class DebugViewQueriesAllowedTests(SimpleTestCase):
-
- databases = {"default"}
- def test_handle_db_exception(self):
- """
- Ensure the debug view works when a database exception is raised by
- performing an invalid query and passing the exception to the debug view.
- """
- with connection.cursor() as cursor:
- try:
- cursor.execute("INVALID SQL")
- except DatabaseError:
- exc_info = sys.exc_info()
- rf = RequestFactory()
- response = technical_500_response(rf.get("/"), *exc_info)
- self.assertContains(response, "OperationalError at /", status_code=500)
- @override_settings(
- DEBUG=True,
- ROOT_URLCONF="view_tests.urls",
-
- TEMPLATES=[
- {
- "BACKEND": "django.template.backends.dummy.TemplateStrings",
- }
- ],
- )
- class NonDjangoTemplatesDebugViewTests(SimpleTestCase):
- def test_400(self):
-
- with self.assertLogs("django.security", "WARNING"):
- response = self.client.get("/raises400/")
- self.assertContains(response, '<div class="context" id="', status_code=400)
- def test_400_bad_request(self):
-
- with self.assertLogs("django.request", "WARNING") as cm:
- response = self.client.get("/raises400_bad_request/")
- self.assertContains(response, '<div class="context" id="', status_code=400)
- self.assertEqual(
- cm.records[0].getMessage(),
- "Malformed request syntax: /raises400_bad_request/",
- )
- def test_403(self):
- response = self.client.get("/raises403/")
- self.assertContains(response, "<h1>403 Forbidden</h1>", status_code=403)
- def test_404(self):
- response = self.client.get("/raises404/")
- self.assertEqual(response.status_code, 404)
- def test_template_not_found_error(self):
-
- url = reverse(
- "raises_template_does_not_exist", kwargs={"path": "notfound.html"}
- )
- with self.assertLogs("django.request", "ERROR"):
- response = self.client.get(url)
- self.assertContains(response, '<div class="context" id="', status_code=500)
- class ExceptionReporterTests(SimpleTestCase):
- rf = RequestFactory()
- def test_request_and_exception(self):
- "A simple exception report can be generated"
- try:
- request = self.rf.get("/test_view/")
- request.user = User()
- raise ValueError("Can't find my keys")
- except ValueError:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(request, exc_type, exc_value, tb)
- html = reporter.get_traceback_html()
- self.assertInHTML("<h1>ValueError at /test_view/</h1>", html)
- self.assertIn(
- '<pre class="exception_value">Can't find my keys</pre>', html
- )
- self.assertIn('<th scope="row">Request Method:</th>', html)
- self.assertIn('<th scope="row">Request URL:</th>', html)
- self.assertIn('<h3 id="user-info">USER</h3>', html)
- self.assertIn("<p>jacob</p>", html)
- self.assertIn('<th scope="row">Exception Type:</th>', html)
- self.assertIn('<th scope="row">Exception Value:</th>', html)
- self.assertIn("<h2>Traceback ", html)
- self.assertIn("<h2>Request information</h2>", html)
- self.assertNotIn("<p>Request data not supplied</p>", html)
- self.assertIn("<p>No POST data</p>", html)
- def test_no_request(self):
- "An exception report can be generated without request"
- try:
- raise ValueError("Can't find my keys")
- except ValueError:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(None, exc_type, exc_value, tb)
- html = reporter.get_traceback_html()
- self.assertInHTML("<h1>ValueError</h1>", html)
- self.assertIn(
- '<pre class="exception_value">Can't find my keys</pre>', html
- )
- self.assertNotIn('<th scope="row">Request Method:</th>', html)
- self.assertNotIn('<th scope="row">Request URL:</th>', html)
- self.assertNotIn('<h3 id="user-info">USER</h3>', html)
- self.assertIn('<th scope="row">Exception Type:</th>', html)
- self.assertIn('<th scope="row">Exception Value:</th>', html)
- self.assertIn("<h2>Traceback ", html)
- self.assertIn("<h2>Request information</h2>", html)
- self.assertIn("<p>Request data not supplied</p>", html)
- def test_sharing_traceback(self):
- try:
- raise ValueError("Oops")
- except ValueError:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(None, exc_type, exc_value, tb)
- html = reporter.get_traceback_html()
- self.assertIn(
- '<form action="https://dpaste.com/" name="pasteform" '
- 'id="pasteform" method="post">',
- html,
- )
- def test_eol_support(self):
- """The ExceptionReporter supports Unix, Windows and Macintosh EOL markers"""
- LINES = ["print %d" % i for i in range(1, 6)]
- reporter = ExceptionReporter(None, None, None, None)
- for newline in ["\n", "\r\n", "\r"]:
- fd, filename = tempfile.mkstemp(text=False)
- os.write(fd, (newline.join(LINES) + newline).encode())
- os.close(fd)
- try:
- self.assertEqual(
- reporter._get_lines_from_file(filename, 3, 2),
- (1, LINES[1:3], LINES[3], LINES[4:]),
- )
- finally:
- os.unlink(filename)
- def test_no_exception(self):
- "An exception report can be generated for just a request"
- request = self.rf.get("/test_view/")
- reporter = ExceptionReporter(request, None, None, None)
- html = reporter.get_traceback_html()
- self.assertInHTML("<h1>Report at /test_view/</h1>", html)
- self.assertIn(
- '<pre class="exception_value">No exception message supplied</pre>', html
- )
- self.assertIn('<th scope="row">Request Method:</th>', html)
- self.assertIn('<th scope="row">Request URL:</th>', html)
- self.assertNotIn('<th scope="row">Exception Type:</th>', html)
- self.assertNotIn('<th scope="row">Exception Value:</th>', html)
- self.assertNotIn("<h2>Traceback ", html)
- self.assertIn("<h2>Request information</h2>", html)
- self.assertNotIn("<p>Request data not supplied</p>", html)
- def test_suppressed_context(self):
- try:
- try:
- raise RuntimeError("Can't find my keys")
- except RuntimeError:
- raise ValueError("Can't find my keys") from None
- except ValueError:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(None, exc_type, exc_value, tb)
- html = reporter.get_traceback_html()
- self.assertInHTML("<h1>ValueError</h1>", html)
- self.assertIn(
- '<pre class="exception_value">Can't find my keys</pre>', html
- )
- self.assertIn('<th scope="row">Exception Type:</th>', html)
- self.assertIn('<th scope="row">Exception Value:</th>', html)
- self.assertIn("<h2>Traceback ", html)
- self.assertIn("<h2>Request information</h2>", html)
- self.assertIn("<p>Request data not supplied</p>", html)
- self.assertNotIn("During handling of the above exception", html)
- def test_innermost_exception_without_traceback(self):
- try:
- try:
- raise RuntimeError("Oops")
- except Exception as exc:
- new_exc = RuntimeError("My context")
- exc.__context__ = new_exc
- raise
- except Exception:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(None, exc_type, exc_value, tb)
- frames = reporter.get_traceback_frames()
- self.assertEqual(len(frames), 2)
- html = reporter.get_traceback_html()
- self.assertInHTML("<h1>RuntimeError</h1>", html)
- self.assertIn('<pre class="exception_value">Oops</pre>', html)
- self.assertIn('<th scope="row">Exception Type:</th>', html)
- self.assertIn('<th scope="row">Exception Value:</th>', html)
- self.assertIn("<h2>Traceback ", html)
- self.assertIn("<h2>Request information</h2>", html)
- self.assertIn("<p>Request data not supplied</p>", html)
- self.assertIn(
- "During handling of the above exception (My context), another "
- "exception occurred",
- html,
- )
- self.assertInHTML('<li class="frame user">None</li>', html)
- self.assertIn("Traceback (most recent call last):\n None", html)
- text = reporter.get_traceback_text()
- self.assertIn("Exception Type: RuntimeError", text)
- self.assertIn("Exception Value: Oops", text)
- self.assertIn("Traceback (most recent call last):\n None", text)
- self.assertIn(
- "During handling of the above exception (My context), another "
- "exception occurred",
- text,
- )
- @skipUnless(PY311, "Exception notes were added in Python 3.11.")
- def test_exception_with_notes(self):
- request = self.rf.get("/test_view/")
- try:
- try:
- raise RuntimeError("Oops")
- except Exception as err:
- err.add_note("First Note")
- err.add_note("Second Note")
- err.add_note(mark_safe("<script>alert(1);</script>"))
- raise err
- except Exception:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(request, exc_type, exc_value, tb)
- html = reporter.get_traceback_html()
- self.assertIn(
- '<pre class="exception_value">Oops\nFirst Note\nSecond Note\n'
- "<script>alert(1);</script></pre>",
- html,
- )
- self.assertIn(
- "Exception Value: Oops\nFirst Note\nSecond Note\n"
- "<script>alert(1);</script>",
- html,
- )
- text = reporter.get_traceback_text()
- self.assertIn(
- "Exception Value: Oops\nFirst Note\nSecond Note\n"
- "<script>alert(1);</script>",
- text,
- )
- def test_mid_stack_exception_without_traceback(self):
- try:
- try:
- raise RuntimeError("Inner Oops")
- except Exception as exc:
- new_exc = RuntimeError("My context")
- new_exc.__context__ = exc
- raise RuntimeError("Oops") from new_exc
- except Exception:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(None, exc_type, exc_value, tb)
- html = reporter.get_traceback_html()
- self.assertInHTML("<h1>RuntimeError</h1>", html)
- self.assertIn('<pre class="exception_value">Oops</pre>', html)
- self.assertIn('<th scope="row">Exception Type:</th>', html)
- self.assertIn('<th scope="row">Exception Value:</th>', html)
- self.assertIn("<h2>Traceback ", html)
- self.assertInHTML('<li class="frame user">Traceback: None</li>', html)
- self.assertIn(
- "During handling of the above exception (Inner Oops), another "
- "exception occurred:\n Traceback: None",
- html,
- )
- text = reporter.get_traceback_text()
- self.assertIn("Exception Type: RuntimeError", text)
- self.assertIn("Exception Value: Oops", text)
- self.assertIn("Traceback (most recent call last):", text)
- self.assertIn(
- "During handling of the above exception (Inner Oops), another "
- "exception occurred:\n Traceback: None",
- text,
- )
- def test_reporting_of_nested_exceptions(self):
- request = self.rf.get("/test_view/")
- try:
- try:
- raise AttributeError(mark_safe("<p>Top level</p>"))
- except AttributeError as explicit:
- try:
- raise ValueError(mark_safe("<p>Second exception</p>")) from explicit
- except ValueError:
- raise IndexError(mark_safe("<p>Final exception</p>"))
- except Exception:
-
- exc_type, exc_value, tb = sys.exc_info()
- explicit_exc = (
- "The above exception ({0}) was the direct cause of the following exception:"
- )
- implicit_exc = (
- "During handling of the above exception ({0}), another exception occurred:"
- )
- reporter = ExceptionReporter(request, exc_type, exc_value, tb)
- html = reporter.get_traceback_html()
-
-
- self.assertEqual(
- 2, html.count(explicit_exc.format("<p>Top level</p>"))
- )
- self.assertEqual(
- 2, html.count(implicit_exc.format("<p>Second exception</p>"))
- )
- self.assertEqual(10, html.count("<p>Final exception</p>"))
- text = reporter.get_traceback_text()
- self.assertIn(explicit_exc.format("<p>Top level</p>"), text)
- self.assertIn(implicit_exc.format("<p>Second exception</p>"), text)
- self.assertEqual(3, text.count("<p>Final exception</p>"))
- @skipIf(
- sys._xoptions.get("no_debug_ranges", False)
- or os.environ.get("PYTHONNODEBUGRANGES", False),
- "Fine-grained error locations are disabled.",
- )
- @skipUnless(PY311, "Fine-grained error locations were added in Python 3.11.")
- def test_highlight_error_position(self):
- request = self.rf.get("/test_view/")
- try:
- try:
- raise AttributeError("Top level")
- except AttributeError as explicit:
- try:
- raise ValueError(mark_safe("<p>2nd exception</p>")) from explicit
- except ValueError:
- raise IndexError("Final exception")
- except Exception:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(request, exc_type, exc_value, tb)
- html = reporter.get_traceback_html()
- self.assertIn(
- "<pre> raise AttributeError("Top level")\n"
- " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</pre>",
- html,
- )
- self.assertIn(
- "<pre> raise ValueError(mark_safe("
- ""<p>2nd exception</p>")) from explicit\n"
- " "
- "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</pre>",
- html,
- )
- self.assertIn(
- "<pre> raise IndexError("Final exception")\n"
- " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</pre>",
- html,
- )
-
- self.assertIn(
- " raise AttributeError("Top level")\n"
- " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
- html,
- )
- self.assertIn(
- " raise ValueError(mark_safe("
- ""<p>2nd exception</p>")) from explicit\n"
- " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
- html,
- )
- self.assertIn(
- " raise IndexError("Final exception")\n"
- " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
- html,
- )
-
- text = reporter.get_traceback_text()
- self.assertIn(
- ' raise AttributeError("Top level")\n'
- " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
- text,
- )
- self.assertIn(
- ' raise ValueError(mark_safe("<p>2nd exception</p>")) from explicit\n'
- " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
- text,
- )
- self.assertIn(
- ' raise IndexError("Final exception")\n'
- " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
- text,
- )
- def test_reporting_frames_without_source(self):
- try:
- source = "def funcName():\n raise Error('Whoops')\nfuncName()"
- namespace = {}
- code = compile(source, "generated", "exec")
- exec(code, namespace)
- except Exception:
- exc_type, exc_value, tb = sys.exc_info()
- request = self.rf.get("/test_view/")
- reporter = ExceptionReporter(request, exc_type, exc_value, tb)
- frames = reporter.get_traceback_frames()
- last_frame = frames[-1]
- self.assertEqual(last_frame["context_line"], "<source code not available>")
- self.assertEqual(last_frame["filename"], "generated")
- self.assertEqual(last_frame["function"], "funcName")
- self.assertEqual(last_frame["lineno"], 2)
- html = reporter.get_traceback_html()
- self.assertIn(
- '<span class="fname">generated</span>, line 2, in funcName',
- html,
- )
- self.assertIn(
- '<code class="fname">generated</code>, line 2, in funcName',
- html,
- )
- self.assertIn(
- '"generated", line 2, in funcName\n <source code not available>',
- html,
- )
- text = reporter.get_traceback_text()
- self.assertIn(
- '"generated", line 2, in funcName\n <source code not available>',
- text,
- )
- def test_reporting_frames_source_not_match(self):
- try:
- source = "def funcName():\n raise Error('Whoops')\nfuncName()"
- namespace = {}
- code = compile(source, "generated", "exec")
- exec(code, namespace)
- except Exception:
- exc_type, exc_value, tb = sys.exc_info()
- with mock.patch(
- "django.views.debug.ExceptionReporter._get_source",
- return_value=["wrong source"],
- ):
- request = self.rf.get("/test_view/")
- reporter = ExceptionReporter(request, exc_type, exc_value, tb)
- frames = reporter.get_traceback_frames()
- last_frame = frames[-1]
- self.assertEqual(last_frame["context_line"], "<source code not available>")
- self.assertEqual(last_frame["filename"], "generated")
- self.assertEqual(last_frame["function"], "funcName")
- self.assertEqual(last_frame["lineno"], 2)
- html = reporter.get_traceback_html()
- self.assertIn(
- '<span class="fname">generated</span>, line 2, in funcName',
- html,
- )
- self.assertIn(
- '<code class="fname">generated</code>, line 2, in funcName',
- html,
- )
- self.assertIn(
- '"generated", line 2, in funcName\n'
- " <source code not available>",
- html,
- )
- text = reporter.get_traceback_text()
- self.assertIn(
- '"generated", line 2, in funcName\n <source code not available>',
- text,
- )
- def test_reporting_frames_for_cyclic_reference(self):
- try:
- def test_func():
- try:
- raise RuntimeError("outer") from RuntimeError("inner")
- except RuntimeError as exc:
- raise exc.__cause__
- test_func()
- except Exception:
- exc_type, exc_value, tb = sys.exc_info()
- request = self.rf.get("/test_view/")
- reporter = ExceptionReporter(request, exc_type, exc_value, tb)
- def generate_traceback_frames(*args, **kwargs):
- nonlocal tb_frames
- tb_frames = reporter.get_traceback_frames()
- tb_frames = None
- tb_generator = threading.Thread(target=generate_traceback_frames, daemon=True)
- msg = (
- "Cycle in the exception chain detected: exception 'inner' "
- "encountered again."
- )
- with self.assertWarnsMessage(ExceptionCycleWarning, msg):
- tb_generator.start()
- tb_generator.join(timeout=5)
- if tb_generator.is_alive():
-
-
-
-
- exc_value.__traceback__ = exc_value.__context__ = exc_value.__cause__ = None
- tb_generator.join()
- self.fail("Cyclic reference in Exception Reporter.get_traceback_frames()")
- if tb_frames is None:
-
-
- self.fail("Traceback generation failed")
- last_frame = tb_frames[-1]
- self.assertIn("raise exc.__cause__", last_frame["context_line"])
- self.assertEqual(last_frame["filename"], __file__)
- self.assertEqual(last_frame["function"], "test_func")
- def test_request_and_message(self):
- "A message can be provided in addition to a request"
- request = self.rf.get("/test_view/")
- reporter = ExceptionReporter(request, None, "I'm a little teapot", None)
- html = reporter.get_traceback_html()
- self.assertInHTML("<h1>Report at /test_view/</h1>", html)
- self.assertIn(
- '<pre class="exception_value">I'm a little teapot</pre>', html
- )
- self.assertIn('<th scope="row">Request Method:</th>', html)
- self.assertIn('<th scope="row">Request URL:</th>', html)
- self.assertNotIn('<th scope="row">Exception Type:</th>', html)
- self.assertNotIn('<th scope="row">Exception Value:</th>', html)
- self.assertIn("<h2>Traceback ", html)
- self.assertIn("<h2>Request information</h2>", html)
- self.assertNotIn("<p>Request data not supplied</p>", html)
- def test_message_only(self):
- reporter = ExceptionReporter(None, None, "I'm a little teapot", None)
- html = reporter.get_traceback_html()
- self.assertInHTML("<h1>Report</h1>", html)
- self.assertIn(
- '<pre class="exception_value">I'm a little teapot</pre>', html
- )
- self.assertNotIn('<th scope="row">Request Method:</th>', html)
- self.assertNotIn('<th scope="row">Request URL:</th>', html)
- self.assertNotIn('<th scope="row">Exception Type:</th>', html)
- self.assertNotIn('<th scope="row">Exception Value:</th>', html)
- self.assertIn("<h2>Traceback ", html)
- self.assertIn("<h2>Request information</h2>", html)
- self.assertIn("<p>Request data not supplied</p>", html)
- def test_non_utf8_values_handling(self):
- "Non-UTF-8 exceptions/values should not make the output generation choke."
- try:
- class NonUtf8Output(Exception):
- def __repr__(self):
- return b"EXC\xe9EXC"
- somevar = b"VAL\xe9VAL"
- raise NonUtf8Output()
- except Exception:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(None, exc_type, exc_value, tb)
- html = reporter.get_traceback_html()
- self.assertIn("VAL\\xe9VAL", html)
- self.assertIn("EXC\\xe9EXC", html)
- def test_local_variable_escaping(self):
- """Safe strings in local variables are escaped."""
- try:
- local = mark_safe("<p>Local variable</p>")
- raise ValueError(local)
- except Exception:
- exc_type, exc_value, tb = sys.exc_info()
- html = ExceptionReporter(None, exc_type, exc_value, tb).get_traceback_html()
- self.assertIn(
- '<td class="code"><pre>'<p>Local variable</p>'</pre>'
- "</td>",
- html,
- )
- def test_unprintable_values_handling(self):
- "Unprintable values should not make the output generation choke."
- try:
- class OomOutput:
- def __repr__(self):
- raise MemoryError("OOM")
- oomvalue = OomOutput()
- raise ValueError()
- except Exception:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(None, exc_type, exc_value, tb)
- html = reporter.get_traceback_html()
- self.assertIn('<td class="code"><pre>Error in formatting', html)
- def test_too_large_values_handling(self):
- "Large values should not create a large HTML."
- large = 256 * 1024
- repr_of_str_adds = len(repr(""))
- try:
- class LargeOutput:
- def __repr__(self):
- return repr("A" * large)
- largevalue = LargeOutput()
- raise ValueError()
- except Exception:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(None, exc_type, exc_value, tb)
- html = reporter.get_traceback_html()
- self.assertEqual(len(html) // 1024 // 128, 0)
- self.assertIn(
- "<trimmed %d bytes string>" % (large + repr_of_str_adds,), html
- )
- def test_encoding_error(self):
- """
- A UnicodeError displays a portion of the problematic string. HTML in
- safe strings is escaped.
- """
- try:
- mark_safe("abcdefghijkl<p>mnὀp</p>qrstuwxyz").encode("ascii")
- except Exception:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(None, exc_type, exc_value, tb)
- html = reporter.get_traceback_html()
- self.assertIn("<h2>Unicode error hint</h2>", html)
- self.assertIn("The string that could not be encoded/decoded was: ", html)
- self.assertIn("<strong><p>mnὀp</p></strong>", html)
- def test_unfrozen_importlib(self):
- """
- importlib is not a frozen app, but its loader thinks it's frozen which
- results in an ImportError. Refs #21443.
- """
- try:
- request = self.rf.get("/test_view/")
- importlib.import_module("abc.def.invalid.name")
- except Exception:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(request, exc_type, exc_value, tb)
- html = reporter.get_traceback_html()
- self.assertInHTML("<h1>ModuleNotFoundError at /test_view/</h1>", html)
- def test_ignore_traceback_evaluation_exceptions(self):
- """
- Don't trip over exceptions generated by crafted objects when
- evaluating them while cleansing (#24455).
- """
- class BrokenEvaluation(Exception):
- pass
- def broken_setup():
- raise BrokenEvaluation
- request = self.rf.get("/test_view/")
- broken_lazy = SimpleLazyObject(broken_setup)
- try:
- bool(broken_lazy)
- except BrokenEvaluation:
- exc_type, exc_value, tb = sys.exc_info()
- self.assertIn(
- "BrokenEvaluation",
- ExceptionReporter(request, exc_type, exc_value, tb).get_traceback_html(),
- "Evaluation exception reason not mentioned in traceback",
- )
- @override_settings(ALLOWED_HOSTS="example.com")
- def test_disallowed_host(self):
- "An exception report can be generated even for a disallowed host."
- request = self.rf.get("/", headers={"host": "evil.com"})
- reporter = ExceptionReporter(request, None, None, None)
- html = reporter.get_traceback_html()
- self.assertIn("http://evil.com/", html)
- def test_request_with_items_key(self):
- """
- An exception report can be generated for requests with 'items' in
- request GET, POST, FILES, or COOKIES QueryDicts.
- """
- value = '<td>items</td><td class="code"><pre>'Oops'</pre></td>'
-
- request = self.rf.get("/test_view/?items=Oops")
- reporter = ExceptionReporter(request, None, None, None)
- html = reporter.get_traceback_html()
- self.assertInHTML(value, html)
-
- request = self.rf.post("/test_view/", data={"items": "Oops"})
- reporter = ExceptionReporter(request, None, None, None)
- html = reporter.get_traceback_html()
- self.assertInHTML(value, html)
-
- fp = StringIO("filecontent")
- request = self.rf.post("/test_view/", data={"name": "filename", "items": fp})
- reporter = ExceptionReporter(request, None, None, None)
- html = reporter.get_traceback_html()
- self.assertInHTML(
- '<td>items</td><td class="code"><pre><InMemoryUploadedFile: '
- "items (application/octet-stream)></pre></td>",
- html,
- )
-
- rf = RequestFactory()
- rf.cookies["items"] = "Oops"
- request = rf.get("/test_view/")
- reporter = ExceptionReporter(request, None, None, None)
- html = reporter.get_traceback_html()
- self.assertInHTML(
- '<td>items</td><td class="code"><pre>'Oops'</pre></td>', html
- )
- def test_exception_fetching_user(self):
- """
- The error page can be rendered if the current user can't be retrieved
- (such as when the database is unavailable).
- """
- class ExceptionUser:
- def __str__(self):
- raise Exception()
- request = self.rf.get("/test_view/")
- request.user = ExceptionUser()
- try:
- raise ValueError("Oops")
- except ValueError:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(request, exc_type, exc_value, tb)
- html = reporter.get_traceback_html()
- self.assertInHTML("<h1>ValueError at /test_view/</h1>", html)
- self.assertIn('<pre class="exception_value">Oops</pre>', html)
- self.assertIn('<h3 id="user-info">USER</h3>', html)
- self.assertIn("<p>[unable to retrieve the current user]</p>", html)
- text = reporter.get_traceback_text()
- self.assertIn("USER: [unable to retrieve the current user]", text)
- def test_template_encoding(self):
- """
- The templates are loaded directly, not via a template loader, and
- should be opened as utf-8 charset as is the default specified on
- template engines.
- """
- reporter = ExceptionReporter(None, None, None, None)
- with mock.patch.object(DebugPath, "open") as m:
- reporter.get_traceback_html()
- m.assert_called_once_with(encoding="utf-8")
- m.reset_mock()
- reporter.get_traceback_text()
- m.assert_called_once_with(encoding="utf-8")
- @override_settings(ALLOWED_HOSTS=["example.com"])
- def test_get_raw_insecure_uri(self):
- factory = RequestFactory(headers={"host": "evil.com"})
- tests = [
- ("////absolute-uri", "http://evil.com//absolute-uri"),
- ("/?foo=bar", "http://evil.com/?foo=bar"),
- ("/path/with:colons", "http://evil.com/path/with:colons"),
- ]
- for url, expected in tests:
- with self.subTest(url=url):
- request = factory.get(url)
- reporter = ExceptionReporter(request, None, None, None)
- self.assertEqual(reporter._get_raw_insecure_uri(), expected)
- class PlainTextReportTests(SimpleTestCase):
- rf = RequestFactory()
- def test_request_and_exception(self):
- "A simple exception report can be generated"
- try:
- request = self.rf.get("/test_view/")
- request.user = User()
- raise ValueError("Can't find my keys")
- except ValueError:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(request, exc_type, exc_value, tb)
- text = reporter.get_traceback_text()
- self.assertIn("ValueError at /test_view/", text)
- self.assertIn("Can't find my keys", text)
- self.assertIn("Request Method:", text)
- self.assertIn("Request URL:", text)
- self.assertIn("USER: jacob", text)
- self.assertIn("Exception Type:", text)
- self.assertIn("Exception Value:", text)
- self.assertIn("Traceback (most recent call last):", text)
- self.assertIn("Request information:", text)
- self.assertNotIn("Request data not supplied", text)
- def test_no_request(self):
- "An exception report can be generated without request"
- try:
- raise ValueError("Can't find my keys")
- except ValueError:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(None, exc_type, exc_value, tb)
- text = reporter.get_traceback_text()
- self.assertIn("ValueError", text)
- self.assertIn("Can't find my keys", text)
- self.assertNotIn("Request Method:", text)
- self.assertNotIn("Request URL:", text)
- self.assertNotIn("USER:", text)
- self.assertIn("Exception Type:", text)
- self.assertIn("Exception Value:", text)
- self.assertIn("Traceback (most recent call last):", text)
- self.assertIn("Request data not supplied", text)
- def test_no_exception(self):
- "An exception report can be generated for just a request"
- request = self.rf.get("/test_view/")
- reporter = ExceptionReporter(request, None, None, None)
- reporter.get_traceback_text()
- def test_request_and_message(self):
- "A message can be provided in addition to a request"
- request = self.rf.get("/test_view/")
- reporter = ExceptionReporter(request, None, "I'm a little teapot", None)
- reporter.get_traceback_text()
- @override_settings(DEBUG=True)
- def test_template_exception(self):
- request = self.rf.get("/test_view/")
- try:
- render(request, "debug/template_error.html")
- except Exception:
- exc_type, exc_value, tb = sys.exc_info()
- reporter = ExceptionReporter(request, exc_type, exc_value, tb)
- text = reporter.get_traceback_text()
- templ_path = Path(
- Path(__file__).parents[1], "templates", "debug", "template_error.html"
- )
- self.assertIn(
- "Template error:\n"
- "In template %(path)s, error at line 2\n"
- " 'cycle' tag requires at least two arguments\n"
- " 1 : Template with error:\n"
- " 2 : {%% cycle %%} \n"
- " 3 : " % {"path": templ_path},
- text,
- )
- def test_request_with_items_key(self):
- """
- An exception report can be generated for requests with 'items' in
- request GET, POST, FILES, or COOKIES QueryDicts.
- """
-
- request = self.rf.get("/test_view/?items=Oops")
- reporter = ExceptionReporter(request, None, None, None)
- text = reporter.get_traceback_text()
- self.assertIn("items = 'Oops'", text)
-
- request = self.rf.post("/test_view/", data={"items": "Oops"})
- reporter = ExceptionReporter(request, None, None, None)
- text = reporter.get_traceback_text()
- self.assertIn("items = 'Oops'", text)
-
- fp = StringIO("filecontent")
- request = self.rf.post("/test_view/", data={"name": "filename", "items": fp})
- reporter = ExceptionReporter(request, None, None, None)
- text = reporter.get_traceback_text()
- self.assertIn("items = <InMemoryUploadedFile:", text)
-
- rf = RequestFactory()
- rf.cookies["items"] = "Oops"
- request = rf.get("/test_view/")
- reporter = ExceptionReporter(request, None, None, None)
- text = reporter.get_traceback_text()
- self.assertIn("items = 'Oops'", text)
- def test_message_only(self):
- reporter = ExceptionReporter(None, None, "I'm a little teapot", None)
- reporter.get_traceback_text()
- @override_settings(ALLOWED_HOSTS="example.com")
- def test_disallowed_host(self):
- "An exception report can be generated even for a disallowed host."
- request = self.rf.get("/", headers={"host": "evil.com"})
- reporter = ExceptionReporter(request, None, None, None)
- text = reporter.get_traceback_text()
- self.assertIn("http://evil.com/", text)
- class ExceptionReportTestMixin:
-
-
- breakfast_data = {
- "sausage-key": "sausage-value",
- "baked-beans-key": "baked-beans-value",
- "hash-brown-key": "hash-brown-value",
- "bacon-key": "bacon-value",
- }
- def verify_unsafe_response(
- self, view, check_for_vars=True, check_for_POST_params=True
- ):
- """
- Asserts that potentially sensitive info are displayed in the response.
- """
- request = self.rf.post("/some_url/", self.breakfast_data)
- if iscoroutinefunction(view):
- response = async_to_sync(view)(request)
- else:
- response = view(request)
- if check_for_vars:
-
- self.assertContains(response, "cooked_eggs", status_code=500)
- self.assertContains(response, "scrambled", status_code=500)
- self.assertContains(response, "sauce", status_code=500)
- self.assertContains(response, "worcestershire", status_code=500)
- if check_for_POST_params:
- for k, v in self.breakfast_data.items():
-
- self.assertContains(response, k, status_code=500)
- self.assertContains(response, v, status_code=500)
- def verify_safe_response(
- self, view, check_for_vars=True, check_for_POST_params=True
- ):
- """
- Asserts that certain sensitive info are not displayed in the response.
- """
- request = self.rf.post("/some_url/", self.breakfast_data)
- if iscoroutinefunction(view):
- response = async_to_sync(view)(request)
- else:
- response = view(request)
- if check_for_vars:
-
- self.assertContains(response, "cooked_eggs", status_code=500)
- self.assertContains(response, "scrambled", status_code=500)
-
- self.assertContains(response, "sauce", status_code=500)
- self.assertNotContains(response, "worcestershire", status_code=500)
- if check_for_POST_params:
- for k in self.breakfast_data:
-
- self.assertContains(response, k, status_code=500)
-
- self.assertContains(response, "baked-beans-value", status_code=500)
- self.assertContains(response, "hash-brown-value", status_code=500)
-
- self.assertNotContains(response, "sausage-value", status_code=500)
- self.assertNotContains(response, "bacon-value", status_code=500)
- def verify_paranoid_response(
- self, view, check_for_vars=True, check_for_POST_params=True
- ):
- """
- Asserts that no variables or POST parameters are displayed in the response.
- """
- request = self.rf.post("/some_url/", self.breakfast_data)
- response = view(request)
- if check_for_vars:
-
- self.assertContains(response, "cooked_eggs", status_code=500)
- self.assertNotContains(response, "scrambled", status_code=500)
- self.assertContains(response, "sauce", status_code=500)
- self.assertNotContains(response, "worcestershire", status_code=500)
- if check_for_POST_params:
- for k, v in self.breakfast_data.items():
-
- self.assertContains(response, k, status_code=500)
-
- self.assertNotContains(response, v, status_code=500)
- def verify_unsafe_email(self, view, check_for_POST_params=True):
- """
- Asserts that potentially sensitive info are displayed in the email report.
- """
- with self.settings(ADMINS=[("Admin", "admin@fattie-breakie.com")]):
- mail.outbox = []
- request = self.rf.post("/some_url/", self.breakfast_data)
- if iscoroutinefunction(view):
- async_to_sync(view)(request)
- else:
- view(request)
- self.assertEqual(len(mail.outbox), 1)
- email = mail.outbox[0]
-
- body_plain = str(email.body)
- self.assertNotIn("cooked_eggs", body_plain)
- self.assertNotIn("scrambled", body_plain)
- self.assertNotIn("sauce", body_plain)
- self.assertNotIn("worcestershire", body_plain)
-
- body_html = str(email.alternatives[0].content)
- self.assertIn("cooked_eggs", body_html)
- self.assertIn("scrambled", body_html)
- self.assertIn("sauce", body_html)
- self.assertIn("worcestershire", body_html)
- if check_for_POST_params:
- for k, v in self.breakfast_data.items():
-
- self.assertIn(k, body_plain)
- self.assertIn(v, body_plain)
- self.assertIn(k, body_html)
- self.assertIn(v, body_html)
- def verify_safe_email(self, view, check_for_POST_params=True):
- """
- Asserts that certain sensitive info are not displayed in the email report.
- """
- with self.settings(ADMINS=[("Admin", "admin@fattie-breakie.com")]):
- mail.outbox = []
- request = self.rf.post("/some_url/", self.breakfast_data)
- if iscoroutinefunction(view):
- async_to_sync(view)(request)
- else:
- view(request)
- self.assertEqual(len(mail.outbox), 1)
- email = mail.outbox[0]
-
- body_plain = str(email.body)
- self.assertNotIn("cooked_eggs", body_plain)
- self.assertNotIn("scrambled", body_plain)
- self.assertNotIn("sauce", body_plain)
- self.assertNotIn("worcestershire", body_plain)
-
- body_html = str(email.alternatives[0].content)
- self.assertIn("cooked_eggs", body_html)
- self.assertIn("scrambled", body_html)
- self.assertIn("sauce", body_html)
- self.assertNotIn("worcestershire", body_html)
- if check_for_POST_params:
- for k in self.breakfast_data:
-
- self.assertIn(k, body_plain)
-
- self.assertIn("baked-beans-value", body_plain)
- self.assertIn("hash-brown-value", body_plain)
- self.assertIn("baked-beans-value", body_html)
- self.assertIn("hash-brown-value", body_html)
-
- self.assertNotIn("sausage-value", body_plain)
- self.assertNotIn("bacon-value", body_plain)
- self.assertNotIn("sausage-value", body_html)
- self.assertNotIn("bacon-value", body_html)
- def verify_paranoid_email(self, view):
- """
- Asserts that no variables or POST parameters are displayed in the email report.
- """
- with self.settings(ADMINS=[("Admin", "admin@fattie-breakie.com")]):
- mail.outbox = []
- request = self.rf.post("/some_url/", self.breakfast_data)
- view(request)
- self.assertEqual(len(mail.outbox), 1)
- email = mail.outbox[0]
-
- body = str(email.body)
- self.assertNotIn("cooked_eggs", body)
- self.assertNotIn("scrambled", body)
- self.assertNotIn("sauce", body)
- self.assertNotIn("worcestershire", body)
- for k, v in self.breakfast_data.items():
-
- self.assertIn(k, body)
-
- self.assertNotIn(v, body)
- @override_settings(ROOT_URLCONF="view_tests.urls")
- class ExceptionReporterFilterTests(
- ExceptionReportTestMixin, LoggingCaptureMixin, SimpleTestCase
- ):
- """
- Sensitive information can be filtered out of error reports (#14614).
- """
- rf = RequestFactory()
- def test_non_sensitive_request(self):
- """
- Everything (request info and frame variables) can bee seen
- in the default error reports for non-sensitive requests.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(non_sensitive_view)
- self.verify_unsafe_email(non_sensitive_view)
- with self.settings(DEBUG=False):
- self.verify_unsafe_response(non_sensitive_view)
- self.verify_unsafe_email(non_sensitive_view)
- def test_sensitive_request(self):
- """
- Sensitive POST parameters and frame variables cannot be
- seen in the default error reports for sensitive requests.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(sensitive_view)
- self.verify_unsafe_email(sensitive_view)
- with self.settings(DEBUG=False):
- self.verify_safe_response(sensitive_view)
- self.verify_safe_email(sensitive_view)
- def test_async_sensitive_request(self):
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(async_sensitive_view)
- self.verify_unsafe_email(async_sensitive_view)
- with self.settings(DEBUG=False):
- self.verify_safe_response(async_sensitive_view)
- self.verify_safe_email(async_sensitive_view)
- def test_async_sensitive_nested_request(self):
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(async_sensitive_view_nested)
- self.verify_unsafe_email(async_sensitive_view_nested)
- with self.settings(DEBUG=False):
- self.verify_safe_response(async_sensitive_view_nested)
- self.verify_safe_email(async_sensitive_view_nested)
- def test_paranoid_request(self):
- """
- No POST parameters and frame variables can be seen in the
- default error reports for "paranoid" requests.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(paranoid_view)
- self.verify_unsafe_email(paranoid_view)
- with self.settings(DEBUG=False):
- self.verify_paranoid_response(paranoid_view)
- self.verify_paranoid_email(paranoid_view)
- def test_multivalue_dict_key_error(self):
- """
- #21098 -- Sensitive POST parameters cannot be seen in the
- error reports for if request.POST['nonexistent_key'] throws an error.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(multivalue_dict_key_error)
- self.verify_unsafe_email(multivalue_dict_key_error)
- with self.settings(DEBUG=False):
- self.verify_safe_response(multivalue_dict_key_error)
- self.verify_safe_email(multivalue_dict_key_error)
- def test_custom_exception_reporter_filter(self):
- """
- It's possible to assign an exception reporter filter to
- the request to bypass the one set in DEFAULT_EXCEPTION_REPORTER_FILTER.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(custom_exception_reporter_filter_view)
- self.verify_unsafe_email(custom_exception_reporter_filter_view)
- with self.settings(DEBUG=False):
- self.verify_unsafe_response(custom_exception_reporter_filter_view)
- self.verify_unsafe_email(custom_exception_reporter_filter_view)
- def test_sensitive_method(self):
- """
- The sensitive_variables decorator works with object methods.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(
- sensitive_method_view, check_for_POST_params=False
- )
- self.verify_unsafe_email(sensitive_method_view, check_for_POST_params=False)
- with self.settings(DEBUG=False):
- self.verify_safe_response(
- sensitive_method_view, check_for_POST_params=False
- )
- self.verify_safe_email(sensitive_method_view, check_for_POST_params=False)
- def test_async_sensitive_method(self):
- """
- The sensitive_variables decorator works with async object methods.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(
- async_sensitive_method_view, check_for_POST_params=False
- )
- self.verify_unsafe_email(
- async_sensitive_method_view, check_for_POST_params=False
- )
- with self.settings(DEBUG=False):
- self.verify_safe_response(
- async_sensitive_method_view, check_for_POST_params=False
- )
- self.verify_safe_email(
- async_sensitive_method_view, check_for_POST_params=False
- )
- def test_async_sensitive_method_nested(self):
- """
- The sensitive_variables decorator works with async object methods.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(
- async_sensitive_method_view_nested, check_for_POST_params=False
- )
- self.verify_unsafe_email(
- async_sensitive_method_view_nested, check_for_POST_params=False
- )
- with self.settings(DEBUG=False):
- self.verify_safe_response(
- async_sensitive_method_view_nested, check_for_POST_params=False
- )
- self.verify_safe_email(
- async_sensitive_method_view_nested, check_for_POST_params=False
- )
- def test_sensitive_function_arguments(self):
- """
- Sensitive variables don't leak in the sensitive_variables decorator's
- frame, when those variables are passed as arguments to the decorated
- function.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(sensitive_args_function_caller)
- self.verify_unsafe_email(sensitive_args_function_caller)
- with self.settings(DEBUG=False):
- self.verify_safe_response(
- sensitive_args_function_caller, check_for_POST_params=False
- )
- self.verify_safe_email(
- sensitive_args_function_caller, check_for_POST_params=False
- )
- def test_sensitive_function_keyword_arguments(self):
- """
- Sensitive variables don't leak in the sensitive_variables decorator's
- frame, when those variables are passed as keyword arguments to the
- decorated function.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(sensitive_kwargs_function_caller)
- self.verify_unsafe_email(sensitive_kwargs_function_caller)
- with self.settings(DEBUG=False):
- self.verify_safe_response(
- sensitive_kwargs_function_caller, check_for_POST_params=False
- )
- self.verify_safe_email(
- sensitive_kwargs_function_caller, check_for_POST_params=False
- )
- def test_callable_settings(self):
- """
- Callable settings should not be evaluated in the debug page (#21345).
- """
- def callable_setting():
- return "This should not be displayed"
- with self.settings(DEBUG=True, FOOBAR=callable_setting):
- response = self.client.get("/raises500/")
- self.assertNotContains(
- response, "This should not be displayed", status_code=500
- )
- def test_callable_settings_forbidding_to_set_attributes(self):
- """
- Callable settings which forbid to set attributes should not break
- the debug page (#23070).
- """
- class CallableSettingWithSlots:
- __slots__ = []
- def __call__(self):
- return "This should not be displayed"
- with self.settings(DEBUG=True, WITH_SLOTS=CallableSettingWithSlots()):
- response = self.client.get("/raises500/")
- self.assertNotContains(
- response, "This should not be displayed", status_code=500
- )
- def test_dict_setting_with_non_str_key(self):
- """
- A dict setting containing a non-string key should not break the
- debug page (#12744).
- """
- with self.settings(DEBUG=True, FOOBAR={42: None}):
- response = self.client.get("/raises500/")
- self.assertContains(response, "FOOBAR", status_code=500)
- def test_sensitive_settings(self):
- """
- The debug page should not show some sensitive settings
- (password, secret key, ...).
- """
- sensitive_settings = [
- "SECRET_KEY",
- "SECRET_KEY_FALLBACKS",
- "PASSWORD",
- "API_KEY",
- "AUTH_TOKEN",
- ]
- for setting in sensitive_settings:
- with self.settings(DEBUG=True, **{setting: "should not be displayed"}):
- response = self.client.get("/raises500/")
- self.assertNotContains(
- response, "should not be displayed", status_code=500
- )
- def test_settings_with_sensitive_keys(self):
- """
- The debug page should filter out some sensitive information found in
- dict settings.
- """
- sensitive_settings = [
- "SECRET_KEY",
- "SECRET_KEY_FALLBACKS",
- "PASSWORD",
- "API_KEY",
- "AUTH_TOKEN",
- ]
- for setting in sensitive_settings:
- FOOBAR = {
- setting: "should not be displayed",
- "recursive": {setting: "should not be displayed"},
- }
- with self.settings(DEBUG=True, FOOBAR=FOOBAR):
- response = self.client.get("/raises500/")
- self.assertNotContains(
- response, "should not be displayed", status_code=500
- )
- def test_cleanse_setting_basic(self):
- reporter_filter = SafeExceptionReporterFilter()
- self.assertEqual(reporter_filter.cleanse_setting("TEST", "TEST"), "TEST")
- self.assertEqual(
- reporter_filter.cleanse_setting("PASSWORD", "super_secret"),
- reporter_filter.cleansed_substitute,
- )
- def test_cleanse_setting_ignore_case(self):
- reporter_filter = SafeExceptionReporterFilter()
- self.assertEqual(
- reporter_filter.cleanse_setting("password", "super_secret"),
- reporter_filter.cleansed_substitute,
- )
- def test_cleanse_setting_recurses_in_dictionary(self):
- reporter_filter = SafeExceptionReporterFilter()
- initial = {"login": "cooper", "password": "secret"}
- self.assertEqual(
- reporter_filter.cleanse_setting("SETTING_NAME", initial),
- {"login": "cooper", "password": reporter_filter.cleansed_substitute},
- )
- def test_cleanse_setting_recurses_in_dictionary_with_non_string_key(self):
- reporter_filter = SafeExceptionReporterFilter()
- initial = {("localhost", 8000): {"login": "cooper", "password": "secret"}}
- self.assertEqual(
- reporter_filter.cleanse_setting("SETTING_NAME", initial),
- {
- ("localhost", 8000): {
- "login": "cooper",
- "password": reporter_filter.cleansed_substitute,
- },
- },
- )
- def test_cleanse_setting_recurses_in_list_tuples(self):
- reporter_filter = SafeExceptionReporterFilter()
- initial = [
- {
- "login": "cooper",
- "password": "secret",
- "apps": (
- {"name": "app1", "api_key": "a06b-c462cffae87a"},
- {"name": "app2", "api_key": "a9f4-f152e97ad808"},
- ),
- "tokens": ["98b37c57-ec62-4e39", "8690ef7d-8004-4916"],
- },
- {"SECRET_KEY": "c4d77c62-6196-4f17-a06b-c462cffae87a"},
- ]
- cleansed = [
- {
- "login": "cooper",
- "password": reporter_filter.cleansed_substitute,
- "apps": (
- {"name": "app1", "api_key": reporter_filter.cleansed_substitute},
- {"name": "app2", "api_key": reporter_filter.cleansed_substitute},
- ),
- "tokens": reporter_filter.cleansed_substitute,
- },
- {"SECRET_KEY": reporter_filter.cleansed_substitute},
- ]
- self.assertEqual(
- reporter_filter.cleanse_setting("SETTING_NAME", initial),
- cleansed,
- )
- self.assertEqual(
- reporter_filter.cleanse_setting("SETTING_NAME", tuple(initial)),
- tuple(cleansed),
- )
- def test_request_meta_filtering(self):
- request = self.rf.get("/", headers={"secret-header": "super_secret"})
- reporter_filter = SafeExceptionReporterFilter()
- self.assertEqual(
- reporter_filter.get_safe_request_meta(request)["HTTP_SECRET_HEADER"],
- reporter_filter.cleansed_substitute,
- )
- def test_exception_report_uses_meta_filtering(self):
- response = self.client.get(
- "/raises500/", headers={"secret-header": "super_secret"}
- )
- self.assertNotIn(b"super_secret", response.content)
- response = self.client.get(
- "/raises500/",
- headers={"secret-header": "super_secret", "accept": "application/json"},
- )
- self.assertNotIn(b"super_secret", response.content)
- @override_settings(SESSION_COOKIE_NAME="djangosession")
- def test_cleanse_session_cookie_value(self):
- self.client.cookies.load({"djangosession": "should not be displayed"})
- response = self.client.get("/raises500/")
- self.assertNotContains(response, "should not be displayed", status_code=500)
- class CustomExceptionReporterFilter(SafeExceptionReporterFilter):
- cleansed_substitute = "XXXXXXXXXXXXXXXXXXXX"
- hidden_settings = _lazy_re_compile(
- "API|TOKEN|KEY|SECRET|PASS|SIGNATURE|DATABASE_URL", flags=re.I
- )
- @override_settings(
- ROOT_URLCONF="view_tests.urls",
- DEFAULT_EXCEPTION_REPORTER_FILTER="%s.CustomExceptionReporterFilter" % __name__,
- )
- class CustomExceptionReporterFilterTests(SimpleTestCase):
- def setUp(self):
- get_default_exception_reporter_filter.cache_clear()
- self.addCleanup(get_default_exception_reporter_filter.cache_clear)
- def test_setting_allows_custom_subclass(self):
- self.assertIsInstance(
- get_default_exception_reporter_filter(),
- CustomExceptionReporterFilter,
- )
- def test_cleansed_substitute_override(self):
- reporter_filter = get_default_exception_reporter_filter()
- self.assertEqual(
- reporter_filter.cleanse_setting("password", "super_secret"),
- reporter_filter.cleansed_substitute,
- )
- def test_hidden_settings_override(self):
- reporter_filter = get_default_exception_reporter_filter()
- self.assertEqual(
- reporter_filter.cleanse_setting("database_url", "super_secret"),
- reporter_filter.cleansed_substitute,
- )
- class NonHTMLResponseExceptionReporterFilter(
- ExceptionReportTestMixin, LoggingCaptureMixin, SimpleTestCase
- ):
- """
- Sensitive information can be filtered out of error reports.
- The plain text 500 debug-only error page is served when it has been
- detected the request doesn't accept HTML content. Don't check for
- (non)existence of frames vars in the traceback information section of the
- response content because they're not included in these error pages.
- Refs #14614.
- """
- rf = RequestFactory(headers={"accept": "application/json"})
- def test_non_sensitive_request(self):
- """
- Request info can bee seen in the default error reports for
- non-sensitive requests.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(non_sensitive_view, check_for_vars=False)
- with self.settings(DEBUG=False):
- self.verify_unsafe_response(non_sensitive_view, check_for_vars=False)
- def test_sensitive_request(self):
- """
- Sensitive POST parameters cannot be seen in the default
- error reports for sensitive requests.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(sensitive_view, check_for_vars=False)
- with self.settings(DEBUG=False):
- self.verify_safe_response(sensitive_view, check_for_vars=False)
- def test_async_sensitive_request(self):
- """
- Sensitive POST parameters cannot be seen in the default
- error reports for sensitive requests.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(async_sensitive_view, check_for_vars=False)
- with self.settings(DEBUG=False):
- self.verify_safe_response(async_sensitive_view, check_for_vars=False)
- def test_async_sensitive_request_nested(self):
- """
- Sensitive POST parameters cannot be seen in the default
- error reports for sensitive requests.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(
- async_sensitive_view_nested, check_for_vars=False
- )
- with self.settings(DEBUG=False):
- self.verify_safe_response(async_sensitive_view_nested, check_for_vars=False)
- def test_paranoid_request(self):
- """
- No POST parameters can be seen in the default error reports
- for "paranoid" requests.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(paranoid_view, check_for_vars=False)
- with self.settings(DEBUG=False):
- self.verify_paranoid_response(paranoid_view, check_for_vars=False)
- def test_custom_exception_reporter_filter(self):
- """
- It's possible to assign an exception reporter filter to
- the request to bypass the one set in DEFAULT_EXCEPTION_REPORTER_FILTER.
- """
- with self.settings(DEBUG=True):
- self.verify_unsafe_response(
- custom_exception_reporter_filter_view, check_for_vars=False
- )
- with self.settings(DEBUG=False):
- self.verify_unsafe_response(
- custom_exception_reporter_filter_view, check_for_vars=False
- )
- @override_settings(DEBUG=True, ROOT_URLCONF="view_tests.urls")
- def test_non_html_response_encoding(self):
- response = self.client.get(
- "/raises500/", headers={"accept": "application/json"}
- )
- self.assertEqual(response.headers["Content-Type"], "text/plain; charset=utf-8")
- class DecoratorsTests(SimpleTestCase):
- def test_sensitive_variables_not_called(self):
- msg = (
- "sensitive_variables() must be called to use it as a decorator, "
- "e.g., use @sensitive_variables(), not @sensitive_variables."
- )
- with self.assertRaisesMessage(TypeError, msg):
- @sensitive_variables
- def test_func(password):
- pass
- def test_sensitive_post_parameters_not_called(self):
- msg = (
- "sensitive_post_parameters() must be called to use it as a "
- "decorator, e.g., use @sensitive_post_parameters(), not "
- "@sensitive_post_parameters."
- )
- with self.assertRaisesMessage(TypeError, msg):
- @sensitive_post_parameters
- def test_func(request):
- return index_page(request)
- def test_sensitive_post_parameters_http_request(self):
- class MyClass:
- @sensitive_post_parameters()
- def a_view(self, request):
- return HttpResponse()
- msg = (
- "sensitive_post_parameters didn't receive an HttpRequest object. "
- "If you are decorating a classmethod, make sure to use "
- "@method_decorator."
- )
- with self.assertRaisesMessage(TypeError, msg):
- MyClass().a_view(HttpRequest())
|