diff options
| author | Carlton Gibson <carlton.gibson@noumenal.es> | 2022-12-13 16:15:25 +0100 |
|---|---|---|
| committer | Carlton Gibson <carlton.gibson@noumenal.es> | 2022-12-22 10:41:12 +0100 |
| commit | 0bd2c0c9015b53c41394a1c0989afbfd94dc2830 (patch) | |
| tree | 6b24758335cf10eeedfdf7dec50cda3500796305 /tests | |
| parent | ae0899be0d787fbfc5f5ab2b18c5a8219d822d2b (diff) | |
Fixed #33735 -- Added async support to StreamingHttpResponse.
Thanks to Florian Vazelle for initial exploratory work, and to Nick
Pope and Mariusz Felisiak for review.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/asgi/tests.py | 11 | ||||
| -rw-r--r-- | tests/httpwrappers/tests.py | 36 | ||||
| -rw-r--r-- | tests/middleware/tests.py | 22 |
3 files changed, 69 insertions, 0 deletions
diff --git a/tests/asgi/tests.py b/tests/asgi/tests.py index 4e51c2d9fe..61d040b45b 100644 --- a/tests/asgi/tests.py +++ b/tests/asgi/tests.py @@ -12,6 +12,7 @@ from django.db import close_old_connections from django.test import ( AsyncRequestFactory, SimpleTestCase, + ignore_warnings, modify_settings, override_settings, ) @@ -58,6 +59,13 @@ class ASGITest(SimpleTestCase): # Allow response.close() to finish. await communicator.wait() + # Python's file API is not async compatible. A third-party library such + # as https://github.com/Tinche/aiofiles allows passing the file to + # FileResponse as an async interator. With a sync iterator + # StreamingHTTPResponse triggers a warning when iterating the file. + # assertWarnsMessage is not async compatible, so ignore_warnings for the + # test. + @ignore_warnings(module="django.http.response") async def test_file_response(self): """ Makes sure that FileResponse works over ASGI. @@ -91,6 +99,8 @@ class ASGITest(SimpleTestCase): self.assertEqual(value, b"text/plain") else: raise + + # Warning ignored here. response_body = await communicator.receive_output() self.assertEqual(response_body["type"], "http.response.body") self.assertEqual(response_body["body"], test_file_contents) @@ -106,6 +116,7 @@ class ASGITest(SimpleTestCase): "django.contrib.staticfiles.finders.FileSystemFinder", ], ) + @ignore_warnings(module="django.http.response") async def test_static_file_response(self): application = ASGIStaticFilesHandler(get_asgi_application()) # Construct HTTP request. diff --git a/tests/httpwrappers/tests.py b/tests/httpwrappers/tests.py index e1920e2eda..fa2c8fd5d2 100644 --- a/tests/httpwrappers/tests.py +++ b/tests/httpwrappers/tests.py @@ -720,6 +720,42 @@ class StreamingHttpResponseTests(SimpleTestCase): '<StreamingHttpResponse status_code=200, "text/html; charset=utf-8">', ) + async def test_async_streaming_response(self): + async def async_iter(): + yield b"hello" + yield b"world" + + r = StreamingHttpResponse(async_iter()) + + chunks = [] + async for chunk in r: + chunks.append(chunk) + self.assertEqual(chunks, [b"hello", b"world"]) + + def test_async_streaming_response_warning(self): + async def async_iter(): + yield b"hello" + yield b"world" + + r = StreamingHttpResponse(async_iter()) + + msg = ( + "StreamingHttpResponse must consume asynchronous iterators in order to " + "serve them synchronously. Use a synchronous iterator instead." + ) + with self.assertWarnsMessage(Warning, msg): + self.assertEqual(list(r), [b"hello", b"world"]) + + async def test_sync_streaming_response_warning(self): + r = StreamingHttpResponse(iter(["hello", "world"])) + + msg = ( + "StreamingHttpResponse must consume synchronous iterators in order to " + "serve them asynchronously. Use an asynchronous iterator instead." + ) + with self.assertWarnsMessage(Warning, msg): + self.assertEqual(b"hello", await r.__aiter__().__anext__()) + class FileCloseTests(SimpleTestCase): def setUp(self): diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py index 1b8efe1a3e..e29d32ad74 100644 --- a/tests/middleware/tests.py +++ b/tests/middleware/tests.py @@ -899,6 +899,28 @@ class GZipMiddlewareTest(SimpleTestCase): self.assertEqual(r.get("Content-Encoding"), "gzip") self.assertFalse(r.has_header("Content-Length")) + async def test_compress_async_streaming_response(self): + """ + Compression is performed on responses with async streaming content. + """ + + async def get_stream_response(request): + async def iterator(): + for chunk in self.sequence: + yield chunk + + resp = StreamingHttpResponse(iterator()) + resp["Content-Type"] = "text/html; charset=UTF-8" + return resp + + r = await GZipMiddleware(get_stream_response)(self.req) + self.assertEqual( + self.decompress(b"".join([chunk async for chunk in r])), + b"".join(self.sequence), + ) + self.assertEqual(r.get("Content-Encoding"), "gzip") + self.assertFalse(r.has_header("Content-Length")) + def test_compress_streaming_response_unicode(self): """ Compression is performed on responses with streaming Unicode content. |
