Skip to content

Commit 809b9a8

Browse files
committed
add tests for async http middleware
1 parent fcae99c commit 809b9a8

File tree

3 files changed

+247
-0
lines changed

3 files changed

+247
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.http import HttpResponse
2+
from django.urls import path
3+
4+
urlpatterns = [
5+
path("", lambda request: HttpResponse("root is here")),
6+
]

tests/test_middlewares/test_http.py

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
import pytest
2+
3+
from django.http import StreamingHttpResponse, HttpResponse
4+
from django.test import AsyncRequestFactory, AsyncClient
5+
6+
from django_async_extensions.middleware.http import AsyncConditionalGetMiddleware
7+
8+
9+
client = AsyncClient()
10+
11+
12+
@pytest.fixture(autouse=True)
13+
def urlconf_setting_set(settings):
14+
old_urlconf = settings.ROOT_URLCONF
15+
settings.ROOT_URLCONF = "test_middlewares.cond_get_urls"
16+
yield settings
17+
settings.ROOT_URLCONF = old_urlconf
18+
19+
20+
class TestConditionalGetMiddleware:
21+
request_factory = AsyncRequestFactory()
22+
23+
@pytest.fixture(autouse=True)
24+
def setup(self):
25+
self.req = self.request_factory.get("/")
26+
self.resp_headers = {}
27+
28+
async def get_response(self, req):
29+
resp = await client.get(req.path_info)
30+
for key, value in self.resp_headers.items():
31+
resp[key] = value
32+
return resp
33+
34+
# Tests for the ETag header
35+
36+
async def test_middleware_calculates_etag(self):
37+
resp = await AsyncConditionalGetMiddleware(self.get_response)(self.req)
38+
assert resp.status_code == 200
39+
assert "" != resp["ETag"]
40+
41+
async def test_middleware_wont_overwrite_etag(self):
42+
self.resp_headers["ETag"] = "eggs"
43+
resp = await AsyncConditionalGetMiddleware(self.get_response)(self.req)
44+
assert resp.status_code == 200
45+
assert "eggs" == resp["ETag"]
46+
47+
async def test_no_etag_streaming_response(self):
48+
async def get_response(req):
49+
return StreamingHttpResponse(["content"])
50+
51+
response = await AsyncConditionalGetMiddleware(get_response)(self.req)
52+
assert response.has_header("ETag") is False
53+
54+
async def test_no_etag_response_empty_content(self):
55+
async def get_response(req):
56+
return HttpResponse()
57+
58+
response = await AsyncConditionalGetMiddleware(get_response)(self.req)
59+
assert response.has_header("ETag") is False
60+
61+
async def test_no_etag_no_store_cache(self):
62+
self.resp_headers["Cache-Control"] = "No-Cache, No-Store, Max-age=0"
63+
response = await AsyncConditionalGetMiddleware(self.get_response)(self.req)
64+
assert response.has_header("ETag") is False
65+
66+
async def test_etag_extended_cache_control(self):
67+
self.resp_headers["Cache-Control"] = 'my-directive="my-no-store"'
68+
response = await AsyncConditionalGetMiddleware(self.get_response)(self.req)
69+
assert response.has_header("ETag")
70+
71+
async def test_if_none_match_and_no_etag(self):
72+
self.req.META["HTTP_IF_NONE_MATCH"] = "spam"
73+
resp = await AsyncConditionalGetMiddleware(self.get_response)(self.req)
74+
assert resp.status_code == 200
75+
76+
async def test_no_if_none_match_and_etag(self):
77+
self.resp_headers["ETag"] = "eggs"
78+
resp = await AsyncConditionalGetMiddleware(self.get_response)(self.req)
79+
assert resp.status_code == 200
80+
81+
async def test_if_none_match_and_same_etag(self):
82+
self.req.META["HTTP_IF_NONE_MATCH"] = '"spam"'
83+
self.resp_headers["ETag"] = '"spam"'
84+
resp = await AsyncConditionalGetMiddleware(self.get_response)(self.req)
85+
assert resp.status_code == 304
86+
87+
async def test_if_none_match_and_different_etag(self):
88+
self.req.META["HTTP_IF_NONE_MATCH"] = "spam"
89+
self.resp_headers["ETag"] = "eggs"
90+
resp = await AsyncConditionalGetMiddleware(self.get_response)(self.req)
91+
assert resp.status_code == 200
92+
93+
async def test_if_none_match_and_redirect(self):
94+
async def get_response(req):
95+
resp = await client.get(req.path_info)
96+
resp["ETag"] = "spam"
97+
resp["Location"] = "/"
98+
resp.status_code = 301
99+
return resp
100+
101+
self.req.META["HTTP_IF_NONE_MATCH"] = "spam"
102+
resp = await AsyncConditionalGetMiddleware(get_response)(self.req)
103+
assert resp.status_code == 301
104+
105+
async def test_if_none_match_and_client_error(self):
106+
async def get_response(req):
107+
resp = await client.get(req.path_info)
108+
resp["ETag"] = "spam"
109+
resp.status_code = 400
110+
return resp
111+
112+
self.req.META["HTTP_IF_NONE_MATCH"] = "spam"
113+
resp = await AsyncConditionalGetMiddleware(get_response)(self.req)
114+
assert resp.status_code == 400
115+
116+
# Tests for the Last-Modified header
117+
118+
async def test_if_modified_since_and_no_last_modified(self):
119+
self.req.META["HTTP_IF_MODIFIED_SINCE"] = "Sat, 12 Feb 2011 17:38:44 GMT"
120+
resp = await AsyncConditionalGetMiddleware(self.get_response)(self.req)
121+
assert resp.status_code == 200
122+
123+
async def test_no_if_modified_since_and_last_modified(self):
124+
self.resp_headers["Last-Modified"] = "Sat, 12 Feb 2011 17:38:44 GMT"
125+
resp = await AsyncConditionalGetMiddleware(self.get_response)(self.req)
126+
assert resp.status_code == 200
127+
128+
async def test_if_modified_since_and_same_last_modified(self):
129+
self.req.META["HTTP_IF_MODIFIED_SINCE"] = "Sat, 12 Feb 2011 17:38:44 GMT"
130+
self.resp_headers["Last-Modified"] = "Sat, 12 Feb 2011 17:38:44 GMT"
131+
self.resp = await AsyncConditionalGetMiddleware(self.get_response)(self.req)
132+
assert self.resp.status_code == 304
133+
134+
async def test_if_modified_since_and_last_modified_in_the_past(self):
135+
self.req.META["HTTP_IF_MODIFIED_SINCE"] = "Sat, 12 Feb 2011 17:38:44 GMT"
136+
self.resp_headers["Last-Modified"] = "Sat, 12 Feb 2011 17:35:44 GMT"
137+
resp = await AsyncConditionalGetMiddleware(self.get_response)(self.req)
138+
assert resp.status_code == 304
139+
140+
async def test_if_modified_since_and_last_modified_in_the_future(self):
141+
self.req.META["HTTP_IF_MODIFIED_SINCE"] = "Sat, 12 Feb 2011 17:38:44 GMT"
142+
self.resp_headers["Last-Modified"] = "Sat, 12 Feb 2011 17:41:44 GMT"
143+
self.resp = await AsyncConditionalGetMiddleware(self.get_response)(self.req)
144+
assert self.resp.status_code == 200
145+
146+
async def test_if_modified_since_and_redirect(self):
147+
async def get_response(req):
148+
resp = await client.get(req.path_info)
149+
resp["Last-Modified"] = "Sat, 12 Feb 2011 17:35:44 GMT"
150+
resp["Location"] = "/"
151+
resp.status_code = 301
152+
return resp
153+
154+
self.req.META["HTTP_IF_MODIFIED_SINCE"] = "Sat, 12 Feb 2011 17:38:44 GMT"
155+
resp = await AsyncConditionalGetMiddleware(get_response)(self.req)
156+
assert resp.status_code == 301
157+
158+
async def test_if_modified_since_and_client_error(self):
159+
async def get_response(req):
160+
resp = await client.get(req.path_info)
161+
resp["Last-Modified"] = "Sat, 12 Feb 2011 17:35:44 GMT"
162+
resp.status_code = 400
163+
return resp
164+
165+
self.req.META["HTTP_IF_MODIFIED_SINCE"] = "Sat, 12 Feb 2011 17:38:44 GMT"
166+
resp = await AsyncConditionalGetMiddleware(get_response)(self.req)
167+
assert resp.status_code == 400
168+
169+
async def test_not_modified_headers(self):
170+
"""
171+
The 304 Not Modified response should include only the headers required
172+
by RFC 9110 Section 15.4.5, Last-Modified, and the cookies.
173+
"""
174+
175+
async def get_response(req):
176+
resp = await client.get(req.path_info)
177+
resp["Date"] = "Sat, 12 Feb 2011 17:35:44 GMT"
178+
resp["Last-Modified"] = "Sat, 12 Feb 2011 17:35:44 GMT"
179+
resp["Expires"] = "Sun, 13 Feb 2011 17:35:44 GMT"
180+
resp["Vary"] = "Cookie"
181+
resp["Cache-Control"] = "public"
182+
resp["Content-Location"] = "/alt"
183+
resp["Content-Language"] = "en" # shouldn't be preserved
184+
resp["ETag"] = '"spam"'
185+
resp.set_cookie("key", "value")
186+
return resp
187+
188+
self.req.META["HTTP_IF_NONE_MATCH"] = '"spam"'
189+
190+
new_response = await AsyncConditionalGetMiddleware(get_response)(self.req)
191+
assert new_response.status_code == 304
192+
base_response = await get_response(self.req)
193+
for header in (
194+
"Cache-Control",
195+
"Content-Location",
196+
"Date",
197+
"ETag",
198+
"Expires",
199+
"Last-Modified",
200+
"Vary",
201+
):
202+
assert new_response.headers[header] == base_response.headers[header]
203+
assert new_response.cookies == base_response.cookies
204+
assert "Content-Language" not in new_response
205+
206+
async def test_no_unsafe(self):
207+
"""
208+
ConditionalGetMiddleware shouldn't return a conditional response on an
209+
unsafe request. A response has already been generated by the time
210+
ConditionalGetMiddleware is called, so it's too late to return a 412
211+
Precondition Failed.
212+
"""
213+
214+
async def get_200_response(req):
215+
return HttpResponse(status=200)
216+
217+
response = await AsyncConditionalGetMiddleware(self.get_response)(self.req)
218+
etag = response.headers["ETag"]
219+
put_request = self.request_factory.put("/", headers={"if-match": etag})
220+
conditional_get_response = await AsyncConditionalGetMiddleware(
221+
get_200_response
222+
)(put_request)
223+
assert conditional_get_response.status_code == 200 # should never be a 412
224+
225+
async def test_no_head(self):
226+
"""
227+
ConditionalGetMiddleware shouldn't compute and return an ETag on a
228+
HEAD request since it can't do so accurately without access to the
229+
response body of the corresponding GET.
230+
"""
231+
232+
async def get_200_response(req):
233+
return HttpResponse(status=200)
234+
235+
request = self.request_factory.head("/")
236+
conditional_get_response = await AsyncConditionalGetMiddleware(
237+
get_200_response
238+
)(request)
239+
assert "ETag" not in conditional_get_response

tests/test_middlewares/test_middleware_mixin.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from django.http.response import HttpResponse
77

88
from django_async_extensions.middleware.base import AsyncMiddlewareMixin
9+
from django_async_extensions.middleware.http import AsyncConditionalGetMiddleware
910
from django_async_extensions.middleware.locale import AsyncLocaleMiddleware
1011
from django_async_extensions.middleware.security import AsyncSecurityMiddleware
1112

@@ -35,6 +36,7 @@ class TestMiddlewareMixin:
3536
middlewares = [
3637
AsyncSecurityMiddleware,
3738
AsyncLocaleMiddleware,
39+
AsyncConditionalGetMiddleware,
3840
]
3941

4042
def test_repr(self):

0 commit comments

Comments
 (0)