url("/write_error", WriteErrorHandler),
url("/get_error_html", GetErrorHtmlHandler),
url("/failed_write_error", FailedWriteErrorHandler),
- ])
+ ]
def test_default(self):
- response = self.fetch("/default")
- self.assertEqual(response.code, 500)
- self.assertTrue(b("500: Internal Server Error") in response.body)
+ with ExpectLog(app_log, "Uncaught exception"):
+ response = self.fetch("/default")
+ self.assertEqual(response.code, 500)
+ self.assertTrue(b"500: Internal Server Error" in response.body)
- response = self.fetch("/default?status=503")
- self.assertEqual(response.code, 503)
- self.assertTrue(b("503: Service Unavailable") in response.body)
+ response = self.fetch("/default?status=503")
+ self.assertEqual(response.code, 503)
+ self.assertTrue(b"503: Service Unavailable" in response.body)
def test_write_error(self):
- response = self.fetch("/write_error")
- self.assertEqual(response.code, 500)
- self.assertEqual(b("Exception: ZeroDivisionError"), response.body)
+ with ExpectLog(app_log, "Uncaught exception"):
+ response = self.fetch("/write_error")
+ self.assertEqual(response.code, 500)
+ self.assertEqual(b"Exception: ZeroDivisionError", response.body)
- response = self.fetch("/write_error?status=503")
- self.assertEqual(response.code, 503)
- self.assertEqual(b("Status: 503"), response.body)
+ response = self.fetch("/write_error?status=503")
+ self.assertEqual(response.code, 503)
+ self.assertEqual(b"Status: 503", response.body)
def test_get_error_html(self):
- response = self.fetch("/get_error_html")
- self.assertEqual(response.code, 500)
- self.assertEqual(b("Exception: ZeroDivisionError"), response.body)
+ with ExpectLog(app_log, "Uncaught exception"):
+ response = self.fetch("/get_error_html")
+ self.assertEqual(response.code, 500)
+ self.assertEqual(b"Exception: ZeroDivisionError", response.body)
- response = self.fetch("/get_error_html?status=503")
- self.assertEqual(response.code, 503)
- self.assertEqual(b("Status: 503"), response.body)
+ response = self.fetch("/get_error_html?status=503")
+ self.assertEqual(response.code, 503)
+ self.assertEqual(b"Status: 503", response.body)
def test_failed_write_error(self):
- response = self.fetch("/failed_write_error")
- self.assertEqual(response.code, 500)
- self.assertEqual(b(""), response.body)
+ with ExpectLog(app_log, "Uncaught exception"):
+ response = self.fetch("/failed_write_error")
+ self.assertEqual(response.code, 500)
+ self.assertEqual(b"", response.body)
-class StaticFileTest(AsyncHTTPTestCase, LogTrapTestCase):
- def get_app(self):
+
+@wsgi_safe
+class StaticFileTest(WebTestCase):
+ # The expected MD5 hash of robots.txt, used in tests that call
+ # StaticFileHandler.get_version
+ robots_txt_hash = b"f71d20196d4caf35b6a670db8c70b03d"
+
+ def get_handlers(self):
class StaticUrlHandler(RequestHandler):
def get(self, path):
- self.write(self.static_url(path))
+ with_v = int(self.get_argument('include_version', 1))
+ self.write(self.static_url(path, include_version=with_v))
- class AbsoluteStaticUrlHandler(RequestHandler):
+ class AbsoluteStaticUrlHandler(StaticUrlHandler):
include_host = True
- def get(self, path):
- self.write(self.static_url(path))
-
class OverrideStaticUrlHandler(RequestHandler):
def get(self, path):
do_include = bool(self.get_argument("include_host"))
def test_absolute_static_url(self):
response = self.fetch("/abs_static_url/robots.txt")
- self.assertEqual(response.body,
- utf8(self.get_url("/") + "static/robots.txt?v=f71d2"))
+ self.assertEqual(response.body, (
+ utf8(self.get_url("/")) +
+ b"static/robots.txt?v=" +
+ self.robots_txt_hash
+ ))
- self.assertEqual(response.body, b("/static/robots.txt"))
+ def test_relative_version_exclusion(self):
+ response = self.fetch("/static_url/robots.txt?include_version=0")
++ self.assertEqual(response.body, b"/static/robots.txt")
+
+ def test_absolute_version_exclusion(self):
+ response = self.fetch("/abs_static_url/robots.txt?include_version=0")
+ self.assertEqual(response.body,
+ utf8(self.get_url("/") + "static/robots.txt"))
+
def test_include_host_override(self):
self._trigger_include_host_check(False)
self._trigger_include_host_check(True)
response = self.fetch(path % int(include_host))
self.assertEqual(response.body, utf8(str(True)))
-class CustomStaticFileTest(AsyncHTTPTestCase, LogTrapTestCase):
- def get_app(self):
- class MyStaticFileHandler(StaticFileHandler):
- def get(self, path):
- path = self.parse_url_path(path)
- assert path == "foo.txt"
- self.write("bar")
+ def test_static_304_if_modified_since(self):
+ response1 = self.fetch("/static/robots.txt")
+ response2 = self.fetch("/static/robots.txt", headers={
+ 'If-Modified-Since': response1.headers['Last-Modified']})
+ self.assertEqual(response2.code, 304)
+ self.assertTrue('Content-Length' not in response2.headers)
+ self.assertTrue('Last-Modified' not in response2.headers)
+
+ def test_static_304_if_none_match(self):
+ response1 = self.fetch("/static/robots.txt")
+ response2 = self.fetch("/static/robots.txt", headers={
+ 'If-None-Match': response1.headers['Etag']})
+ self.assertEqual(response2.code, 304)
+
+ def test_static_if_modified_since_pre_epoch(self):
+ # On windows, the functions that work with time_t do not accept
+ # negative values, and at least one client (processing.js) seems
+ # to use if-modified-since 1/1/1960 as a cache-busting technique.
+ response = self.fetch("/static/robots.txt", headers={
+ 'If-Modified-Since': 'Fri, 01 Jan 1960 00:00:00 GMT'})
+ self.assertEqual(response.code, 200)
+
+ def test_static_if_modified_since_time_zone(self):
+ # Instead of the value from Last-Modified, make requests with times
+ # chosen just before and after the known modification time
+ # of the file to ensure that the right time zone is being used
+ # when parsing If-Modified-Since.
+ stat = os.stat(os.path.join(os.path.dirname(__file__),
+ 'static/robots.txt'))
+
+ response = self.fetch('/static/robots.txt', headers={
+ 'If-Modified-Since': format_timestamp(stat.st_mtime - 1)})
+ self.assertEqual(response.code, 200)
+ response = self.fetch('/static/robots.txt', headers={
+ 'If-Modified-Since': format_timestamp(stat.st_mtime + 1)})
+ self.assertEqual(response.code, 304)
+
+ def test_static_etag(self):
+ response = self.fetch('/static/robots.txt')
+ self.assertEqual(utf8(response.headers.get("Etag")),
+ b'"' + self.robots_txt_hash + b'"')
+
+ def test_static_with_range(self):
+ response = self.fetch('/static/robots.txt', headers={
+ 'Range': 'bytes=0-9'})
+ self.assertEqual(response.code, 206)
+ self.assertEqual(response.body, b"User-agent")
+ self.assertEqual(utf8(response.headers.get("Etag")),
+ b'"' + self.robots_txt_hash + b'"')
+ self.assertEqual(response.headers.get("Content-Length"), "10")
+ self.assertEqual(response.headers.get("Content-Range"),
+ "0-9/26")
+
+ def test_static_with_range_end_edge(self):
+ response = self.fetch('/static/robots.txt', headers={
+ 'Range': 'bytes=22-'})
+ self.assertEqual(response.body, b": /\n")
+ self.assertEqual(response.headers.get("Content-Length"), "4")
+ self.assertEqual(response.headers.get("Content-Range"),
+ "22-25/26")
+
+ def test_static_with_range_neg_end(self):
+ response = self.fetch('/static/robots.txt', headers={
+ 'Range': 'bytes=-4'})
+ self.assertEqual(response.body, b": /\n")
+ self.assertEqual(response.headers.get("Content-Length"), "4")
+ self.assertEqual(response.headers.get("Content-Range"),
+ "22-25/26")
+
+ def test_static_invalid_range(self):
+ response = self.fetch('/static/robots.txt', headers={
+ 'Range': 'asdf'})
+ self.assertEqual(response.code, 416)
+
+ def test_static_head(self):
+ response = self.fetch('/static/robots.txt', method='HEAD')
+ self.assertEqual(response.code, 200)
+ # No body was returned, but we did get the right content length.
+ self.assertEqual(response.body, b'')
+ self.assertEqual(response.headers['Content-Length'], '26')
+ self.assertEqual(utf8(response.headers['Etag']),
+ b'"' + self.robots_txt_hash + b'"')
+
+ def test_static_head_range(self):
+ response = self.fetch('/static/robots.txt', method='HEAD',
+ headers={'Range': 'bytes=1-4'})
+ self.assertEqual(response.code, 206)
+ self.assertEqual(response.body, b'')
+ self.assertEqual(response.headers['Content-Length'], '4')
+ self.assertEqual(utf8(response.headers['Etag']),
+ b'"' + self.robots_txt_hash + b'"')
+
+ def test_static_range_if_none_match(self):
+ response = self.fetch('/static/robots.txt', headers={
+ 'Range': 'bytes=1-4',
+ 'If-None-Match': b'"' + self.robots_txt_hash + b'"'})
+ self.assertEqual(response.code, 304)
+ self.assertEqual(response.body, b'')
+ self.assertTrue('Content-Length' not in response.headers)
+ self.assertEqual(utf8(response.headers['Etag']),
+ b'"' + self.robots_txt_hash + b'"')
+
+ def test_static_404(self):
+ response = self.fetch('/static/blarg')
+ self.assertEqual(response.code, 404)
+
+
+@wsgi_safe
+class StaticDefaultFilenameTest(WebTestCase):
+ def get_app_kwargs(self):
+ return dict(static_path=os.path.join(os.path.dirname(__file__),
+ 'static'),
+ static_handler_args=dict(default_filename='index.html'))
+
+ def get_handlers(self):
+ return []
+
+ def test_static_default_filename(self):
+ response = self.fetch('/static/dir/', follow_redirects=False)
+ self.assertEqual(response.code, 200)
+ self.assertEqual(b'this is the index\n', response.body)
+
+ def test_static_default_redirect(self):
+ response = self.fetch('/static/dir', follow_redirects=False)
+ self.assertEqual(response.code, 301)
+ self.assertTrue(response.headers['Location'].endswith('/static/dir/'))
+
+
+@wsgi_safe
+class CustomStaticFileTest(WebTestCase):
+ def get_handlers(self):
+ class MyStaticFileHandler(StaticFileHandler):
@classmethod
- def make_static_url(cls, settings, path):
- version = cls.get_version(settings, path)
+ def make_static_url(cls, settings, path, include_version=True):
+ version_hash = cls.get_version(settings, path)
extension_index = path.rindex('.')
before_version = path[:extension_index]
after_version = path[(extension_index + 1):]
- return '/static/%s.%s.%s' % (before_version, version,
- return '/static/%s.%s.%s' % (before_version, 42, after_version)
++ return '/static/%s.%s.%s' % (before_version, version_hash,
+ after_version)
- @classmethod
- def parse_url_path(cls, url_path):
+ def parse_url_path(self, url_path):
extension_index = url_path.rindex('.')
version_index = url_path.rindex('.', 0, extension_index)
return '%s%s' % (url_path[:version_index],