Skip to content

Commit 0e0e805

Browse files
committed
Merge branch 'add_jitter_validate_keyringitem' into keyring_in_database
2 parents fb68cb1 + de11336 commit 0e0e805

5 files changed

Lines changed: 74 additions & 26 deletions

File tree

testproject/home/tests.py

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ def hook_any(obj, is_cacheable: bool):
3939

4040

4141
class WagtailCacheTest(TestCase):
42+
# Django's default `testserver` is not a valid domain name.
43+
client_headers = {"SERVER_NAME": "example.com"}
44+
4245
@classmethod
4346
def get_content_type(cls, modelname: str):
4447
ctype, _ = ContentType.objects.get_or_create(
@@ -145,23 +148,23 @@ def head_hit(self, url: str):
145148
"""
146149
HEAD a page and test that it was served from the cache.
147150
"""
148-
response = self.client.head(url)
151+
response = self.client.head(url, **self.client_headers)
149152
self.assertEqual(response.get(self.header_name, None), Status.HIT.value)
150153
return response
151154

152155
def get_hit(self, url: str):
153156
"""
154157
Gets a page and tests that it was served from the cache.
155158
"""
156-
response = self.client.get(url)
159+
response = self.client.get(url, **self.client_headers)
157160
self.assertEqual(response.get(self.header_name, None), Status.HIT.value)
158161
return response
159162

160163
def head_miss(self, url: str):
161164
"""
162165
HEAD a page and test that it was not served from the cache.
163166
"""
164-
response = self.client.head(url)
167+
response = self.client.head(url, **self.client_headers)
165168
self.assertEqual(
166169
response.get(self.header_name, None), Status.MISS.value
167170
)
@@ -170,7 +173,7 @@ def get_miss(self, url: str):
170173
"""
171174
Gets a page and tests that it was not served from the cache.
172175
"""
173-
response = self.client.get(url)
176+
response = self.client.get(url, **self.client_headers)
174177
self.assertEqual(
175178
response.get(self.header_name, None), Status.MISS.value
176179
)
@@ -181,7 +184,7 @@ def head_skip(self, url: str):
181184
HEAD a page and test that it was intentionally not served from the
182185
cache.
183186
"""
184-
response = self.client.head(url)
187+
response = self.client.head(url, **self.client_headers)
185188
self.assertEqual(
186189
response.get(self.header_name, None), Status.SKIP.value
187190
)
@@ -195,7 +198,7 @@ def get_skip(self, url: str):
195198
Gets a page and tests that it was intentionally not served from
196199
the cache.
197200
"""
198-
response = self.client.get(url)
201+
response = self.client.get(url, **self.client_headers)
199202
self.assertEqual(
200203
response.get(self.header_name, None), Status.SKIP.value
201204
)
@@ -209,7 +212,7 @@ def get_error(self, url: str):
209212
"""
210213
Gets a page and tests that an error in the cache backend was handled.
211214
"""
212-
response = self.client.get(url)
215+
response = self.client.get(url, **self.client_headers)
213216
self.assertEqual(response.status_code, 200)
214217
self.assertEqual(
215218
response.get(self.header_name, None), Status.ERROR.value
@@ -220,7 +223,7 @@ def head_error(self, url: str):
220223
"""
221224
HEAD a page and tests that an error in the cache backend was handled.
222225
"""
223-
response = self.client.head(url)
226+
response = self.client.head(url, **self.client_headers)
224227
self.assertEqual(response.status_code, 200)
225228
self.assertEqual(
226229
response.get(self.header_name, None), Status.ERROR.value
@@ -232,7 +235,7 @@ def post_skip(self, url: str):
232235
POSTS a page and tests that it was intentionally not served from
233236
the cache.
234237
"""
235-
response = self.client.post(url)
238+
response = self.client.post(url, **self.client_headers)
236239
self.assertEqual(
237240
response.get(self.header_name, None), Status.SKIP.value
238241
)
@@ -292,6 +295,9 @@ def test_querystrings(self):
292295
# A get with both should also hit, since it is the second request.
293296
self.head_hit(page.get_url() + "?valid=0&utm_code=0")
294297
self.get_hit(page.get_url() + "?valid=0&utm_code=0")
298+
# A get with a very long querysting should return an error
299+
self.head_error(page.get_url() + "?" + "a" * 2000)
300+
self.get_error(page.get_url() + "?" + "a" * 2000)
295301

296302
@override_settings(WAGTAIL_CACHE_IGNORE_COOKIES=False)
297303
def test_cookie_page(self):
@@ -384,6 +390,7 @@ def test_page_restricted(self):
384390
"password": "the cybers",
385391
"return_url": self.page_cachedpage_restricted.get_url(),
386392
},
393+
**self.client_headers,
387394
)
388395
self.assertRedirects(
389396
response, self.page_cachedpage_restricted.get_url()
@@ -493,7 +500,9 @@ def test_template_response_view_hit(self):
493500

494501
def test_admin(self):
495502
self.client.force_login(self.user)
496-
response = self.client.get(reverse("wagtailcache:index"))
503+
response = self.client.get(
504+
reverse("wagtailcache:index"), **self.client_headers
505+
)
497506
self.client.logout()
498507
self.assertEqual(response.status_code, 200)
499508

@@ -504,7 +513,9 @@ def test_admin_clearcache(self):
504513
self.get_hit(self.page_cachedpage.get_url())
505514
# Now log in as admin and clear the cache.
506515
self.client.force_login(self.user)
507-
response = self.client.get(reverse("wagtailcache:clearcache"))
516+
response = self.client.get(
517+
reverse("wagtailcache:clearcache"), **self.client_headers
518+
)
508519
self.client.logout()
509520
self.assertEqual(response.status_code, 302)
510521
# Now the page should miss cache.
@@ -519,7 +530,7 @@ def test_cache_keyring(self):
519530
self.get_miss(self.page_cachedpage.get_url())
520531
self.assertEqual(KeyringItem.objects.count(), 1)
521532
# Get first key from keyring
522-
url = "http://%s%s" % ("testserver", self.page_cachedpage.get_url())
533+
url = "http://%s%s" % ("example.com", self.page_cachedpage.get_url())
523534
keyring_item = KeyringItem.objects.active_for_url_regexes(url).first()
524535
# Compare Keys
525536
self.assertEqual(keyring_item.url, url)
@@ -569,22 +580,30 @@ def test_clear_cache_url(self):
569580
@override_settings(WAGTAIL_CACHE=True)
570581
def test_enable_wagtailcache(self):
571582
# Intentionally enable wagtail-cache, make sure it works.
572-
response = self.client.get(self.page_cachedpage.get_url())
583+
response = self.client.get(
584+
self.page_cachedpage.get_url(), **self.client_headers
585+
)
573586
self.assertIsNotNone(response.get(self.header_name, None))
574587

575588
@override_settings(WAGTAIL_CACHE=False)
576589
def test_disable_wagtailcache(self):
577590
# Intentionally disable wagtail-cache, make sure it is inactive.
578-
response = self.client.get(self.page_cachedpage.get_url())
591+
response = self.client.get(
592+
self.page_cachedpage.get_url(), **self.client_headers
593+
)
579594
self.assertIsNone(response.get(self.header_name, None))
580595

581596
@override_settings(WAGTAIL_CACHE_BACKEND="zero")
582597
def test_zero_timeout(self):
583598
# Wagtail-cache should ignore the page when a timeout is zero.
584-
response = self.client.get(self.page_cachedpage.get_url())
599+
response = self.client.get(
600+
self.page_cachedpage.get_url(), **self.client_headers
601+
)
585602
self.assertIsNone(response.get(self.header_name, None))
586603
# Second should also not cache.
587-
response = self.client.get(self.page_cachedpage.get_url())
604+
response = self.client.get(
605+
self.page_cachedpage.get_url(), **self.client_headers
606+
)
588607
self.assertIsNone(response.get(self.header_name, None))
589608
# Load admin panel to render the zero timeout.
590609
self.test_admin()
@@ -611,15 +630,30 @@ def test_page_error_set(self):
611630
self.head_error(page.get_url())
612631
self.get_error(page.get_url())
613632

633+
@override_settings(
634+
WAGTAIL_CACHE_BACKEND="one_second",
635+
WAGTAIL_CACHE_TIMEOUT_JITTER_FUNC=lambda timeout: timeout * 2,
636+
)
637+
def test_timeout_jitter(self):
638+
# Wagtail-cache should apply jitter to the timeout.
639+
url = self.page_cachedpage.get_url()
640+
self.client.get(url, **self.client_headers)
641+
time.sleep(1.5)
642+
self.get_hit(url)
643+
614644
# ---- HOOKS ---------------------------------------------------------------
615645

616646
def test_request_hook_true(self):
617647
# A POST should never be cached.
618-
response = self.client.post(reverse("cached_view"))
648+
response = self.client.post(
649+
reverse("cached_view"), **self.client_headers
650+
)
619651
self.assertEqual(
620652
response.get(self.header_name, None), Status.SKIP.value
621653
)
622-
response = self.client.post(reverse("cached_view"))
654+
response = self.client.post(
655+
reverse("cached_view"), **self.client_headers
656+
)
623657
self.assertEqual(
624658
response.get(self.header_name, None), Status.SKIP.value
625659
)
@@ -631,11 +665,15 @@ def test_request_hook_true(self):
631665
# the response still has the final say in whether or not the response is
632666
# cached. However a simple POST request where the response does not
633667
# forbid caching will in fact get cached!
634-
response = self.client.post(reverse("cached_view"))
668+
response = self.client.post(
669+
reverse("cached_view"), **self.client_headers
670+
)
635671
self.assertEqual(
636672
response.get(self.header_name, None), Status.MISS.value
637673
)
638-
response = self.client.post(reverse("cached_view"))
674+
response = self.client.post(
675+
reverse("cached_view"), **self.client_headers
676+
)
639677
self.assertEqual(response.get(self.header_name, None), Status.HIT.value)
640678

641679
def test_request_hook_false(self):

wagtailcache/cache.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,10 @@ def process_response(
332332
timeout = get_max_age(response)
333333
if timeout is None:
334334
timeout = self._wagcache.default_timeout
335+
if wagtailcache_settings.WAGTAIL_CACHE_TIMEOUT_JITTER_FUNC:
336+
timeout = wagtailcache_settings.WAGTAIL_CACHE_TIMEOUT_JITTER_FUNC(
337+
timeout
338+
)
335339
patch_response_headers(response, timeout)
336340
if timeout:
337341
try:

wagtailcache/management/commands/clear_wagtail_expired_cache_items.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from django.core.management.base import BaseCommand
2+
23
from wagtailcache.models import KeyringItem
34

5+
46
class Command(BaseCommand):
5-
help = 'Clear expired KeyringItems from the database'
7+
help = "Clear expired KeyringItems from the database"
68

79
def handle(self, *args, **options):
810
try:

wagtailcache/models.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@ def set(self, url, key, expiry) -> "KeyringItem":
1919
"""
2020
Create or update a keyring item, clearing expired items too.
2121
"""
22-
item, _ = self.update_or_create(
23-
defaults={"expiry": expiry},
24-
url=url,
25-
key=key,
26-
)
22+
# Ensure `full_clean` is called to validate the model.
23+
try:
24+
item = self.select_for_update().get(url=url, key=key)
25+
item.expiry = expiry
26+
except KeyringItem.DoesNotExist:
27+
item = KeyringItem(url=url, key=key, expiry=expiry)
28+
item.full_clean()
29+
item.save()
2730

2831
if wagtailcache_settings.WAGTAIL_CACHE_CLEAR_EXPIRED_ON_SET:
2932
self.clear_expired()

wagtailcache/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class _DefaultSettings:
3636
r"^utm_.*$", # Google Analytics
3737
]
3838
WAGTAIL_CACHE_CLEAR_EXPIRED_ON_SET = False
39+
WAGTAIL_CACHE_TIMEOUT_JITTER_FUNC = None
3940
WAGTAIL_CACHE_USE_RAW_DELETE = False
4041

4142
def __getattribute__(self, attr: Text):

0 commit comments

Comments
 (0)